reline 0.1.5 → 0.3.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/README.md +50 -0
- data/lib/reline/ansi.rb +206 -58
- data/lib/reline/config.rb +71 -21
- data/lib/reline/general_io.rb +31 -3
- data/lib/reline/key_actor/base.rb +12 -0
- data/lib/reline/key_actor/emacs.rb +2 -2
- data/lib/reline/key_actor/vi_command.rb +2 -2
- data/lib/reline/key_stroke.rb +64 -14
- data/lib/reline/kill_ring.rb +12 -0
- data/lib/reline/line_editor.rb +1332 -291
- data/lib/reline/sibori.rb +170 -0
- data/lib/reline/terminfo.rb +171 -0
- data/lib/reline/unicode/east_asian_width.rb +1149 -1130
- data/lib/reline/unicode.rb +103 -33
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +324 -109
- data/lib/reline.rb +184 -39
- data/license_of_rb-readline +25 -0
- metadata +6 -45
data/lib/reline/line_editor.rb
CHANGED
@@ -5,6 +5,7 @@ require 'tempfile'
|
|
5
5
|
|
6
6
|
class Reline::LineEditor
|
7
7
|
# TODO: undo
|
8
|
+
# TODO: Use "private alias_method" idiom after drop Ruby 2.5.
|
8
9
|
attr_reader :line
|
9
10
|
attr_reader :byte_pointer
|
10
11
|
attr_accessor :confirm_multiline_termination_proc
|
@@ -50,13 +51,49 @@ class Reline::LineEditor
|
|
50
51
|
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
|
51
52
|
MenuInfo = Struct.new('MenuInfo', :target, :list)
|
52
53
|
|
54
|
+
PROMPT_LIST_CACHE_TIMEOUT = 0.5
|
55
|
+
|
53
56
|
def initialize(config, encoding)
|
54
57
|
@config = config
|
55
58
|
@completion_append_character = ''
|
56
59
|
reset_variables(encoding: encoding)
|
57
60
|
end
|
58
61
|
|
59
|
-
|
62
|
+
def set_pasting_state(in_pasting)
|
63
|
+
@in_pasting = in_pasting
|
64
|
+
end
|
65
|
+
|
66
|
+
def simplified_rendering?
|
67
|
+
if finished?
|
68
|
+
false
|
69
|
+
elsif @just_cursor_moving and not @rerender_all
|
70
|
+
true
|
71
|
+
else
|
72
|
+
not @rerender_all and not finished? and @in_pasting
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private def check_mode_string
|
77
|
+
mode_string = nil
|
78
|
+
if @config.show_mode_in_prompt
|
79
|
+
if @config.editing_mode_is?(:vi_command)
|
80
|
+
mode_string = @config.vi_cmd_mode_string
|
81
|
+
elsif @config.editing_mode_is?(:vi_insert)
|
82
|
+
mode_string = @config.vi_ins_mode_string
|
83
|
+
elsif @config.editing_mode_is?(:emacs)
|
84
|
+
mode_string = @config.emacs_mode_string
|
85
|
+
else
|
86
|
+
mode_string = '?'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
if mode_string != @prev_mode_string
|
90
|
+
@rerender_all = true
|
91
|
+
end
|
92
|
+
@prev_mode_string = mode_string
|
93
|
+
mode_string
|
94
|
+
end
|
95
|
+
|
96
|
+
private def check_multiline_prompt(buffer)
|
60
97
|
if @vi_arg
|
61
98
|
prompt = "(arg: #{@vi_arg}) "
|
62
99
|
@rerender_all = true
|
@@ -66,38 +103,45 @@ class Reline::LineEditor
|
|
66
103
|
else
|
67
104
|
prompt = @prompt
|
68
105
|
end
|
106
|
+
if simplified_rendering?
|
107
|
+
mode_string = check_mode_string
|
108
|
+
prompt = mode_string + prompt if mode_string
|
109
|
+
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
110
|
+
end
|
69
111
|
if @prompt_proc
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
mode_icon = @config.vi_ins_mode_icon
|
77
|
-
elsif @config.editing_mode_is?(:emacs)
|
78
|
-
mode_icon = @config.emacs_mode_string
|
79
|
-
else
|
80
|
-
mode_icon = '?'
|
112
|
+
use_cached_prompt_list = false
|
113
|
+
if @cached_prompt_list
|
114
|
+
if @just_cursor_moving
|
115
|
+
use_cached_prompt_list = true
|
116
|
+
elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
|
117
|
+
use_cached_prompt_list = true
|
81
118
|
end
|
82
|
-
prompt_list.map!{ |pr| mode_icon + pr }
|
83
119
|
end
|
120
|
+
use_cached_prompt_list = false if @rerender_all
|
121
|
+
if use_cached_prompt_list
|
122
|
+
prompt_list = @cached_prompt_list
|
123
|
+
else
|
124
|
+
prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
|
125
|
+
@prompt_cache_time = Time.now.to_f
|
126
|
+
end
|
127
|
+
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
|
128
|
+
prompt_list = [prompt] if prompt_list.empty?
|
129
|
+
mode_string = check_mode_string
|
130
|
+
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
|
84
131
|
prompt = prompt_list[@line_index]
|
132
|
+
prompt = prompt_list[0] if prompt.nil?
|
133
|
+
prompt = prompt_list.last if prompt.nil?
|
134
|
+
if buffer.size > prompt_list.size
|
135
|
+
(buffer.size - prompt_list.size).times do
|
136
|
+
prompt_list << prompt_list.last
|
137
|
+
end
|
138
|
+
end
|
85
139
|
prompt_width = calculate_width(prompt, true)
|
86
140
|
[prompt, prompt_width, prompt_list]
|
87
141
|
else
|
142
|
+
mode_string = check_mode_string
|
143
|
+
prompt = mode_string + prompt if mode_string
|
88
144
|
prompt_width = calculate_width(prompt, true)
|
89
|
-
if @config.show_mode_in_prompt
|
90
|
-
if @config.editing_mode_is?(:vi_command)
|
91
|
-
mode_icon = @config.vi_cmd_mode_icon
|
92
|
-
elsif @config.editing_mode_is?(:vi_insert)
|
93
|
-
mode_icon = @config.vi_ins_mode_icon
|
94
|
-
elsif @config.editing_mode_is?(:emacs)
|
95
|
-
mode_icon = @config.emacs_mode_string
|
96
|
-
else
|
97
|
-
mode_icon = '?'
|
98
|
-
end
|
99
|
-
prompt = mode_icon + prompt
|
100
|
-
end
|
101
145
|
[prompt, prompt_width, nil]
|
102
146
|
end
|
103
147
|
end
|
@@ -105,51 +149,110 @@ class Reline::LineEditor
|
|
105
149
|
def reset(prompt = '', encoding:)
|
106
150
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
107
151
|
@screen_size = Reline::IOGate.get_screen_size
|
152
|
+
@screen_height = @screen_size.first
|
108
153
|
reset_variables(prompt, encoding: encoding)
|
109
|
-
@old_trap = Signal.trap('SIGINT') {
|
110
|
-
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
|
111
|
-
raise Interrupt
|
112
|
-
}
|
113
154
|
Reline::IOGate.set_winch_handler do
|
114
|
-
@
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
155
|
+
@resized = true
|
156
|
+
end
|
157
|
+
if ENV.key?('RELINE_ALT_SCROLLBAR')
|
158
|
+
@full_block = '::'
|
159
|
+
@upper_half_block = "''"
|
160
|
+
@lower_half_block = '..'
|
161
|
+
@block_elem_width = 2
|
162
|
+
elsif Reline::IOGate.win?
|
163
|
+
@full_block = '█'
|
164
|
+
@upper_half_block = '▀'
|
165
|
+
@lower_half_block = '▄'
|
166
|
+
@block_elem_width = 1
|
167
|
+
elsif @encoding == Encoding::UTF_8
|
168
|
+
@full_block = '█'
|
169
|
+
@upper_half_block = '▀'
|
170
|
+
@lower_half_block = '▄'
|
171
|
+
@block_elem_width = Reline::Unicode.calculate_width('█')
|
172
|
+
else
|
173
|
+
@full_block = '::'
|
174
|
+
@upper_half_block = "''"
|
175
|
+
@lower_half_block = '..'
|
176
|
+
@block_elem_width = 2
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def resize
|
181
|
+
return unless @resized
|
182
|
+
@resized = false
|
183
|
+
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
184
|
+
old_screen_size = @screen_size
|
185
|
+
@screen_size = Reline::IOGate.get_screen_size
|
186
|
+
@screen_height = @screen_size.first
|
187
|
+
if old_screen_size.last < @screen_size.last # columns increase
|
188
|
+
@rerender_all = true
|
189
|
+
rerender
|
190
|
+
else
|
191
|
+
back = 0
|
192
|
+
new_buffer = whole_lines
|
193
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
|
194
|
+
new_buffer.each_with_index do |line, index|
|
195
|
+
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
196
|
+
width = prompt_width + calculate_width(line)
|
197
|
+
height = calculate_height_by_width(width)
|
198
|
+
back += height
|
199
|
+
end
|
200
|
+
@highest_in_all = back
|
201
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
202
|
+
@first_line_started_from =
|
203
|
+
if @line_index.zero?
|
204
|
+
0
|
205
|
+
else
|
206
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
141
207
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
208
|
+
if @prompt_proc
|
209
|
+
prompt = prompt_list[@line_index]
|
210
|
+
prompt_width = calculate_width(prompt, true)
|
211
|
+
end
|
212
|
+
calculate_nearest_cursor
|
213
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
214
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
215
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
216
|
+
@rerender_all = true
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def set_signal_handlers
|
221
|
+
@old_trap = Signal.trap('INT') {
|
222
|
+
clear_dialog
|
223
|
+
if @scroll_partial_screen
|
224
|
+
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
225
|
+
else
|
226
|
+
move_cursor_down(@highest_in_all - @line_index - 1)
|
227
|
+
end
|
228
|
+
Reline::IOGate.move_cursor_column(0)
|
229
|
+
scroll_down(1)
|
230
|
+
case @old_trap
|
231
|
+
when 'DEFAULT', 'SYSTEM_DEFAULT'
|
232
|
+
raise Interrupt
|
233
|
+
when 'IGNORE'
|
234
|
+
# Do nothing
|
235
|
+
when 'EXIT'
|
236
|
+
exit
|
237
|
+
else
|
238
|
+
@old_trap.call if @old_trap.respond_to?(:call)
|
147
239
|
end
|
240
|
+
}
|
241
|
+
begin
|
242
|
+
@old_tstp_trap = Signal.trap('TSTP') {
|
243
|
+
Reline::IOGate.ungetc("\C-z".ord)
|
244
|
+
@old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
|
245
|
+
}
|
246
|
+
rescue ArgumentError
|
148
247
|
end
|
149
248
|
end
|
150
249
|
|
151
250
|
def finalize
|
152
|
-
Signal.trap('
|
251
|
+
Signal.trap('INT', @old_trap)
|
252
|
+
begin
|
253
|
+
Signal.trap('TSTP', @old_tstp_trap)
|
254
|
+
rescue ArgumentError
|
255
|
+
end
|
153
256
|
end
|
154
257
|
|
155
258
|
def eof?
|
@@ -157,7 +260,7 @@ class Reline::LineEditor
|
|
157
260
|
end
|
158
261
|
|
159
262
|
def reset_variables(prompt = '', encoding:)
|
160
|
-
@prompt = prompt
|
263
|
+
@prompt = prompt.gsub("\n", "\\n")
|
161
264
|
@mark_pointer = nil
|
162
265
|
@encoding = encoding
|
163
266
|
@is_multiline = false
|
@@ -165,11 +268,12 @@ class Reline::LineEditor
|
|
165
268
|
@cleared = false
|
166
269
|
@rerender_all = false
|
167
270
|
@history_pointer = nil
|
168
|
-
@kill_ring
|
271
|
+
@kill_ring ||= Reline::KillRing.new
|
169
272
|
@vi_clipboard = ''
|
170
273
|
@vi_arg = nil
|
171
274
|
@waiting_proc = nil
|
172
275
|
@waiting_operator_proc = nil
|
276
|
+
@waiting_operator_vi_arg = nil
|
173
277
|
@completion_journey_data = nil
|
174
278
|
@completion_state = CompletionState::NORMAL
|
175
279
|
@perfect_matched = nil
|
@@ -177,7 +281,20 @@ class Reline::LineEditor
|
|
177
281
|
@first_prompt = true
|
178
282
|
@searching_prompt = nil
|
179
283
|
@first_char = true
|
284
|
+
@add_newline_to_end_of_buffer = false
|
285
|
+
@just_cursor_moving = nil
|
286
|
+
@cached_prompt_list = nil
|
287
|
+
@prompt_cache_time = nil
|
180
288
|
@eof = false
|
289
|
+
@continuous_insertion_buffer = String.new(encoding: @encoding)
|
290
|
+
@scroll_partial_screen = nil
|
291
|
+
@prev_mode_string = nil
|
292
|
+
@drop_terminate_spaces = false
|
293
|
+
@in_pasting = false
|
294
|
+
@auto_indent_proc = nil
|
295
|
+
@dialogs = []
|
296
|
+
@last_key = nil
|
297
|
+
@resized = false
|
181
298
|
reset_line
|
182
299
|
end
|
183
300
|
|
@@ -222,6 +339,7 @@ class Reline::LineEditor
|
|
222
339
|
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
|
223
340
|
@previous_line_index = @line_index
|
224
341
|
@line_index += 1
|
342
|
+
@just_cursor_moving = false
|
225
343
|
end
|
226
344
|
|
227
345
|
private def calculate_height_by_width(width)
|
@@ -262,28 +380,29 @@ class Reline::LineEditor
|
|
262
380
|
end
|
263
381
|
end
|
264
382
|
|
265
|
-
private def calculate_nearest_cursor
|
266
|
-
|
383
|
+
private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
|
384
|
+
new_cursor_max = calculate_width(line_to_calc)
|
267
385
|
new_cursor = 0
|
268
386
|
new_byte_pointer = 0
|
269
387
|
height = 1
|
270
388
|
max_width = @screen_size.last
|
271
389
|
if @config.editing_mode_is?(:vi_command)
|
272
|
-
last_byte_size = Reline::Unicode.get_prev_mbchar_size(
|
390
|
+
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
|
273
391
|
if last_byte_size > 0
|
274
|
-
last_mbchar =
|
392
|
+
last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
|
275
393
|
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
|
276
|
-
|
394
|
+
end_of_line_cursor = new_cursor_max - last_width
|
277
395
|
else
|
278
|
-
|
396
|
+
end_of_line_cursor = new_cursor_max
|
279
397
|
end
|
280
398
|
else
|
281
|
-
|
399
|
+
end_of_line_cursor = new_cursor_max
|
282
400
|
end
|
283
|
-
|
284
|
-
|
401
|
+
line_to_calc.grapheme_clusters.each do |gc|
|
402
|
+
mbchar = gc.encode(Encoding::UTF_8)
|
403
|
+
mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
|
285
404
|
now = new_cursor + mbchar_width
|
286
|
-
if now >
|
405
|
+
if now > end_of_line_cursor or now > cursor
|
287
406
|
break
|
288
407
|
end
|
289
408
|
new_cursor += mbchar_width
|
@@ -292,9 +411,21 @@ class Reline::LineEditor
|
|
292
411
|
end
|
293
412
|
new_byte_pointer += gc.bytesize
|
294
413
|
end
|
295
|
-
|
296
|
-
|
297
|
-
|
414
|
+
new_started_from = height - 1
|
415
|
+
if update
|
416
|
+
@cursor = new_cursor
|
417
|
+
@cursor_max = new_cursor_max
|
418
|
+
@started_from = new_started_from
|
419
|
+
@byte_pointer = new_byte_pointer
|
420
|
+
else
|
421
|
+
[new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def rerender_all
|
426
|
+
@rerender_all = true
|
427
|
+
process_insert(force: true)
|
428
|
+
rerender
|
298
429
|
end
|
299
430
|
|
300
431
|
def rerender
|
@@ -302,178 +433,731 @@ class Reline::LineEditor
|
|
302
433
|
if @menu_info
|
303
434
|
scroll_down(@highest_in_all - @first_line_started_from)
|
304
435
|
@rerender_all = true
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
@output.flush
|
309
|
-
scroll_down(1)
|
310
|
-
end
|
311
|
-
scroll_down(@highest_in_all - 1)
|
312
|
-
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
|
436
|
+
end
|
437
|
+
if @menu_info
|
438
|
+
show_menu
|
313
439
|
@menu_info = nil
|
314
440
|
end
|
315
|
-
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines
|
441
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
316
442
|
if @cleared
|
317
|
-
|
443
|
+
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
318
444
|
@cleared = false
|
319
|
-
back = 0
|
320
|
-
modify_lines(whole_lines).each_with_index do |line, index|
|
321
|
-
if @prompt_proc
|
322
|
-
pr = prompt_list[index]
|
323
|
-
height = render_partial(pr, calculate_width(pr), line, false)
|
324
|
-
else
|
325
|
-
height = render_partial(prompt, prompt_width, line, false)
|
326
|
-
end
|
327
|
-
if index < (@buffer_of_lines.size - 1)
|
328
|
-
move_cursor_down(height)
|
329
|
-
back += height
|
330
|
-
end
|
331
|
-
end
|
332
|
-
move_cursor_up(back)
|
333
|
-
move_cursor_down(@first_line_started_from + @started_from)
|
334
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
335
445
|
return
|
336
446
|
end
|
337
|
-
|
338
|
-
|
339
|
-
|
447
|
+
if @is_multiline and finished? and @scroll_partial_screen
|
448
|
+
# Re-output all code higher than the screen when finished.
|
449
|
+
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
|
450
|
+
Reline::IOGate.move_cursor_column(0)
|
451
|
+
@scroll_partial_screen = nil
|
452
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
340
453
|
if @previous_line_index
|
341
454
|
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
342
455
|
else
|
343
456
|
new_lines = whole_lines
|
344
457
|
end
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
458
|
+
modify_lines(new_lines).each_with_index do |line, index|
|
459
|
+
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
|
460
|
+
Reline::IOGate.erase_after_cursor
|
461
|
+
end
|
462
|
+
@output.flush
|
463
|
+
clear_dialog
|
464
|
+
return
|
465
|
+
end
|
466
|
+
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
467
|
+
rendered = false
|
468
|
+
if @add_newline_to_end_of_buffer
|
469
|
+
rerender_added_newline(prompt, prompt_width)
|
470
|
+
@add_newline_to_end_of_buffer = false
|
471
|
+
else
|
472
|
+
if @just_cursor_moving and not @rerender_all
|
473
|
+
rendered = just_move_cursor
|
474
|
+
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
475
|
+
@just_cursor_moving = false
|
476
|
+
return
|
477
|
+
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
478
|
+
rerender_changed_current_line
|
479
|
+
@previous_line_index = nil
|
480
|
+
rendered = true
|
481
|
+
elsif @rerender_all
|
482
|
+
rerender_all_lines
|
483
|
+
@rerender_all = false
|
484
|
+
rendered = true
|
485
|
+
else
|
486
|
+
end
|
487
|
+
end
|
488
|
+
if @is_multiline
|
489
|
+
if finished?
|
490
|
+
# Always rerender on finish because output_modifier_proc may return a different output.
|
491
|
+
if @previous_line_index
|
492
|
+
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
493
|
+
else
|
494
|
+
new_lines = whole_lines
|
357
495
|
end
|
358
|
-
|
496
|
+
line = modify_lines(new_lines)[@line_index]
|
497
|
+
clear_dialog
|
498
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
499
|
+
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
500
|
+
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
501
|
+
scroll_down(1)
|
502
|
+
Reline::IOGate.move_cursor_column(0)
|
503
|
+
Reline::IOGate.erase_after_cursor
|
359
504
|
else
|
360
|
-
|
505
|
+
if not rendered and not @in_pasting
|
506
|
+
line = modify_lines(whole_lines)[@line_index]
|
507
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
508
|
+
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
509
|
+
end
|
510
|
+
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
361
511
|
end
|
362
|
-
@
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
512
|
+
@buffer_of_lines[@line_index] = @line
|
513
|
+
@rest_height = 0 if @scroll_partial_screen
|
514
|
+
else
|
515
|
+
line = modify_lines(whole_lines)[@line_index]
|
516
|
+
render_partial(prompt, prompt_width, line, 0)
|
517
|
+
if finished?
|
518
|
+
scroll_down(1)
|
519
|
+
Reline::IOGate.move_cursor_column(0)
|
520
|
+
Reline::IOGate.erase_after_cursor
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
class DialogProcScope
|
526
|
+
def initialize(line_editor, config, proc_to_exec, context)
|
527
|
+
@line_editor = line_editor
|
528
|
+
@config = config
|
529
|
+
@proc_to_exec = proc_to_exec
|
530
|
+
@context = context
|
531
|
+
@cursor_pos = Reline::CursorPos.new
|
532
|
+
end
|
533
|
+
|
534
|
+
def context
|
535
|
+
@context
|
536
|
+
end
|
537
|
+
|
538
|
+
def retrieve_completion_block(set_completion_quote_character = false)
|
539
|
+
@line_editor.retrieve_completion_block(set_completion_quote_character)
|
540
|
+
end
|
541
|
+
|
542
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
543
|
+
@line_editor.call_completion_proc_with_checking_args(pre, target, post)
|
544
|
+
end
|
545
|
+
|
546
|
+
def set_dialog(dialog)
|
547
|
+
@dialog = dialog
|
548
|
+
end
|
549
|
+
|
550
|
+
def dialog
|
551
|
+
@dialog
|
552
|
+
end
|
553
|
+
|
554
|
+
def set_cursor_pos(col, row)
|
555
|
+
@cursor_pos.x = col
|
556
|
+
@cursor_pos.y = row
|
557
|
+
end
|
558
|
+
|
559
|
+
def set_key(key)
|
560
|
+
@key = key
|
561
|
+
end
|
562
|
+
|
563
|
+
def key
|
564
|
+
@key
|
565
|
+
end
|
566
|
+
|
567
|
+
def cursor_pos
|
568
|
+
@cursor_pos
|
569
|
+
end
|
570
|
+
|
571
|
+
def just_cursor_moving
|
572
|
+
@line_editor.instance_variable_get(:@just_cursor_moving)
|
573
|
+
end
|
574
|
+
|
575
|
+
def screen_width
|
576
|
+
@line_editor.instance_variable_get(:@screen_size).last
|
577
|
+
end
|
578
|
+
|
579
|
+
def completion_journey_data
|
580
|
+
@line_editor.instance_variable_get(:@completion_journey_data)
|
581
|
+
end
|
582
|
+
|
583
|
+
def config
|
584
|
+
@config
|
585
|
+
end
|
586
|
+
|
587
|
+
def call
|
588
|
+
instance_exec(&@proc_to_exec)
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
class Dialog
|
593
|
+
attr_reader :name, :contents, :width
|
594
|
+
attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
|
595
|
+
|
596
|
+
def initialize(name, config, proc_scope)
|
597
|
+
@name = name
|
598
|
+
@config = config
|
599
|
+
@proc_scope = proc_scope
|
600
|
+
@width = nil
|
601
|
+
@scroll_top = 0
|
602
|
+
@trap_key = nil
|
603
|
+
end
|
604
|
+
|
605
|
+
def set_cursor_pos(col, row)
|
606
|
+
@proc_scope.set_cursor_pos(col, row)
|
607
|
+
end
|
608
|
+
|
609
|
+
def width=(v)
|
610
|
+
@width = v
|
611
|
+
end
|
612
|
+
|
613
|
+
def contents=(contents)
|
614
|
+
@contents = contents
|
615
|
+
if contents and @width.nil?
|
616
|
+
@width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def call(key)
|
621
|
+
@proc_scope.set_dialog(self)
|
622
|
+
@proc_scope.set_key(key)
|
623
|
+
dialog_render_info = @proc_scope.call
|
624
|
+
if @trap_key
|
625
|
+
if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
|
626
|
+
@trap_key.each do |t|
|
627
|
+
@config.add_oneshot_key_binding(t, @name)
|
628
|
+
end
|
629
|
+
elsif @trap_key.is_a?(Array)
|
630
|
+
@config.add_oneshot_key_binding(@trap_key, @name)
|
631
|
+
elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
|
632
|
+
@config.add_oneshot_key_binding([@trap_key], @name)
|
368
633
|
end
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
634
|
+
end
|
635
|
+
dialog_render_info
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def add_dialog_proc(name, p, context = nil)
|
640
|
+
dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
|
641
|
+
if index = @dialogs.find_index { |d| d.name == name }
|
642
|
+
@dialogs[index] = dialog
|
643
|
+
else
|
644
|
+
@dialogs << dialog
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
DIALOG_DEFAULT_HEIGHT = 20
|
649
|
+
private def render_dialog(cursor_column)
|
650
|
+
@dialogs.each do |dialog|
|
651
|
+
render_each_dialog(dialog, cursor_column)
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
private def padding_space_with_escape_sequences(str, width)
|
656
|
+
str + (' ' * (width - calculate_width(str, true)))
|
657
|
+
end
|
658
|
+
|
659
|
+
private def render_each_dialog(dialog, cursor_column)
|
660
|
+
if @in_pasting
|
661
|
+
clear_each_dialog(dialog)
|
662
|
+
dialog.contents = nil
|
663
|
+
dialog.trap_key = nil
|
664
|
+
return
|
665
|
+
end
|
666
|
+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
667
|
+
dialog_render_info = dialog.call(@last_key)
|
668
|
+
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
|
669
|
+
dialog.lines_backup = {
|
670
|
+
lines: modify_lines(whole_lines),
|
671
|
+
line_index: @line_index,
|
672
|
+
first_line_started_from: @first_line_started_from,
|
673
|
+
started_from: @started_from,
|
674
|
+
byte_pointer: @byte_pointer
|
675
|
+
}
|
676
|
+
clear_each_dialog(dialog)
|
677
|
+
dialog.contents = nil
|
678
|
+
dialog.trap_key = nil
|
679
|
+
return
|
680
|
+
end
|
681
|
+
old_dialog = dialog.clone
|
682
|
+
dialog.contents = dialog_render_info.contents
|
683
|
+
pointer = dialog.pointer
|
684
|
+
if dialog_render_info.width
|
685
|
+
dialog.width = dialog_render_info.width
|
686
|
+
else
|
687
|
+
dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
|
688
|
+
end
|
689
|
+
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
|
690
|
+
height = dialog.contents.size if dialog.contents.size < height
|
691
|
+
if dialog.contents.size > height
|
692
|
+
if dialog.pointer
|
693
|
+
if dialog.pointer < 0
|
694
|
+
dialog.scroll_top = 0
|
695
|
+
elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
|
696
|
+
dialog.scroll_top = dialog.pointer - (height - 1)
|
697
|
+
elsif (dialog.pointer - dialog.scroll_top) < 0
|
698
|
+
dialog.scroll_top = dialog.pointer
|
375
699
|
end
|
700
|
+
pointer = dialog.pointer - dialog.scroll_top
|
376
701
|
end
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
702
|
+
dialog.contents = dialog.contents[dialog.scroll_top, height]
|
703
|
+
end
|
704
|
+
if dialog.contents and dialog.scroll_top >= dialog.contents.size
|
705
|
+
dialog.scroll_top = dialog.contents.size - height
|
706
|
+
end
|
707
|
+
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
|
708
|
+
bar_max_height = height * 2
|
709
|
+
moving_distance = (dialog_render_info.contents.size - height) * 2
|
710
|
+
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
|
711
|
+
bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
|
712
|
+
dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
713
|
+
else
|
714
|
+
dialog.scrollbar_pos = nil
|
715
|
+
end
|
716
|
+
upper_space = @first_line_started_from - @started_from
|
717
|
+
dialog.column = dialog_render_info.pos.x
|
718
|
+
dialog.width += @block_elem_width if dialog.scrollbar_pos
|
719
|
+
diff = (dialog.column + dialog.width) - (@screen_size.last)
|
720
|
+
if diff > 0
|
721
|
+
dialog.column -= diff
|
722
|
+
end
|
723
|
+
if (@rest_height - dialog_render_info.pos.y) >= height
|
724
|
+
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
725
|
+
elsif upper_space >= height
|
726
|
+
dialog.vertical_offset = dialog_render_info.pos.y - height
|
727
|
+
else
|
728
|
+
if (@rest_height - dialog_render_info.pos.y) < height
|
729
|
+
scroll_down(height + dialog_render_info.pos.y)
|
730
|
+
move_cursor_up(height + dialog_render_info.pos.y)
|
731
|
+
end
|
732
|
+
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
733
|
+
end
|
734
|
+
Reline::IOGate.hide_cursor
|
735
|
+
if dialog.column < 0
|
736
|
+
dialog.column = 0
|
737
|
+
dialog.width = @screen_size.last
|
738
|
+
end
|
739
|
+
reset_dialog(dialog, old_dialog)
|
740
|
+
move_cursor_down(dialog.vertical_offset)
|
741
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
742
|
+
dialog.contents.each_with_index do |item, i|
|
743
|
+
if i == pointer
|
744
|
+
bg_color = '45'
|
745
|
+
else
|
746
|
+
if dialog_render_info.bg_color
|
747
|
+
bg_color = dialog_render_info.bg_color
|
748
|
+
else
|
749
|
+
bg_color = '46'
|
750
|
+
end
|
381
751
|
end
|
382
|
-
|
383
|
-
|
384
|
-
|
752
|
+
str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
|
753
|
+
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
754
|
+
@output.write "\e[#{bg_color}m#{str}"
|
755
|
+
if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
|
756
|
+
@output.write "\e[37m"
|
757
|
+
if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
|
758
|
+
@output.write @full_block
|
759
|
+
elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
|
760
|
+
@output.write @upper_half_block
|
761
|
+
str += ''
|
762
|
+
elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
|
763
|
+
@output.write @lower_half_block
|
385
764
|
else
|
386
|
-
|
765
|
+
@output.write ' ' * @block_elem_width
|
387
766
|
end
|
388
|
-
if @prompt_proc
|
389
|
-
prompt = prompt_list[@line_index]
|
390
|
-
prompt_width = calculate_width(prompt, true)
|
391
767
|
end
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
768
|
+
@output.write "\e[0m"
|
769
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
770
|
+
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
771
|
+
end
|
772
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
773
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
774
|
+
Reline::IOGate.show_cursor
|
775
|
+
dialog.lines_backup = {
|
776
|
+
lines: modify_lines(whole_lines),
|
777
|
+
line_index: @line_index,
|
778
|
+
first_line_started_from: @first_line_started_from,
|
779
|
+
started_from: @started_from,
|
780
|
+
byte_pointer: @byte_pointer
|
781
|
+
}
|
782
|
+
end
|
783
|
+
|
784
|
+
private def reset_dialog(dialog, old_dialog)
|
785
|
+
return if dialog.lines_backup.nil? or old_dialog.contents.nil?
|
786
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
787
|
+
visual_lines = []
|
788
|
+
visual_start = nil
|
789
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
790
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
791
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
792
|
+
vl.compact!
|
793
|
+
if i == dialog.lines_backup[:line_index]
|
794
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
411
795
|
end
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
796
|
+
visual_lines.concat(vl)
|
797
|
+
}
|
798
|
+
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
799
|
+
y = @first_line_started_from + @started_from
|
800
|
+
y_diff = y - old_y
|
801
|
+
if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
|
802
|
+
# rerender top
|
803
|
+
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
804
|
+
start = visual_start + old_dialog.vertical_offset
|
805
|
+
line_num = dialog.vertical_offset - old_dialog.vertical_offset
|
806
|
+
line_num.times do |i|
|
807
|
+
Reline::IOGate.move_cursor_column(old_dialog.column)
|
808
|
+
if visual_lines[start + i].nil?
|
809
|
+
s = ' ' * old_dialog.width
|
810
|
+
else
|
811
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
812
|
+
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
421
813
|
end
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
814
|
+
@output.write "\e[0m#{s}\e[0m"
|
815
|
+
move_cursor_down(1) if i < (line_num - 1)
|
816
|
+
end
|
817
|
+
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
818
|
+
end
|
819
|
+
if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
820
|
+
# rerender bottom
|
821
|
+
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
822
|
+
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
823
|
+
line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
824
|
+
line_num.times do |i|
|
825
|
+
Reline::IOGate.move_cursor_column(old_dialog.column)
|
826
|
+
if visual_lines[start + i].nil?
|
827
|
+
s = ' ' * old_dialog.width
|
828
|
+
else
|
829
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
830
|
+
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
428
831
|
end
|
429
|
-
|
430
|
-
if
|
431
|
-
|
832
|
+
@output.write "\e[0m#{s}\e[0m"
|
833
|
+
move_cursor_down(1) if i < (line_num - 1)
|
834
|
+
end
|
835
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
836
|
+
end
|
837
|
+
if old_dialog.column < dialog.column
|
838
|
+
# rerender left
|
839
|
+
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
840
|
+
width = dialog.column - old_dialog.column
|
841
|
+
start = visual_start + old_dialog.vertical_offset
|
842
|
+
line_num = old_dialog.contents.size
|
843
|
+
line_num.times do |i|
|
844
|
+
Reline::IOGate.move_cursor_column(old_dialog.column)
|
845
|
+
if visual_lines[start + i].nil?
|
846
|
+
s = ' ' * width
|
847
|
+
else
|
848
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
|
849
|
+
s = padding_space_with_escape_sequences(s, dialog.width)
|
432
850
|
end
|
851
|
+
@output.write "\e[0m#{s}\e[0m"
|
852
|
+
move_cursor_down(1) if i < (line_num - 1)
|
853
|
+
end
|
854
|
+
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
855
|
+
end
|
856
|
+
if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
|
857
|
+
# rerender right
|
858
|
+
move_cursor_down(old_dialog.vertical_offset + y_diff)
|
859
|
+
width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
|
860
|
+
start = visual_start + old_dialog.vertical_offset
|
861
|
+
line_num = old_dialog.contents.size
|
862
|
+
line_num.times do |i|
|
863
|
+
Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
|
864
|
+
if visual_lines[start + i].nil?
|
865
|
+
s = ' ' * width
|
866
|
+
else
|
867
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
|
868
|
+
rerender_width = old_dialog.width - dialog.width
|
869
|
+
s = padding_space_with_escape_sequences(s, rerender_width)
|
870
|
+
end
|
871
|
+
Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
|
872
|
+
@output.write "\e[0m#{s}\e[0m"
|
873
|
+
move_cursor_down(1) if i < (line_num - 1)
|
433
874
|
end
|
434
|
-
move_cursor_up(
|
435
|
-
|
436
|
-
|
437
|
-
|
875
|
+
move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
|
876
|
+
end
|
877
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
878
|
+
end
|
879
|
+
|
880
|
+
private def clear_dialog
|
881
|
+
@dialogs.each do |dialog|
|
882
|
+
clear_each_dialog(dialog)
|
883
|
+
end
|
884
|
+
end
|
885
|
+
|
886
|
+
private def clear_each_dialog(dialog)
|
887
|
+
dialog.trap_key = nil
|
888
|
+
return unless dialog.contents
|
889
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
|
890
|
+
visual_lines = []
|
891
|
+
visual_lines_under_dialog = []
|
892
|
+
visual_start = nil
|
893
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
894
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
895
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
896
|
+
vl.compact!
|
897
|
+
if i == dialog.lines_backup[:line_index]
|
898
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
438
899
|
end
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
900
|
+
visual_lines.concat(vl)
|
901
|
+
}
|
902
|
+
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
903
|
+
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
904
|
+
Reline::IOGate.hide_cursor
|
905
|
+
move_cursor_down(dialog.vertical_offset)
|
906
|
+
dialog_vertical_size = dialog.contents.size
|
907
|
+
dialog_vertical_size.times do |i|
|
908
|
+
if i < visual_lines_under_dialog.size
|
909
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
910
|
+
str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
|
911
|
+
str = padding_space_with_escape_sequences(str, dialog.width)
|
912
|
+
@output.write "\e[0m#{str}\e[0m"
|
913
|
+
else
|
914
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
915
|
+
@output.write "\e[0m#{' ' * dialog.width}\e[0m"
|
916
|
+
end
|
917
|
+
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
918
|
+
end
|
919
|
+
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
920
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
921
|
+
Reline::IOGate.show_cursor
|
922
|
+
end
|
923
|
+
|
924
|
+
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
925
|
+
if @screen_height < highest_in_all
|
926
|
+
old_scroll_partial_screen = @scroll_partial_screen
|
927
|
+
if cursor_y == 0
|
928
|
+
@scroll_partial_screen = 0
|
929
|
+
elsif cursor_y == (highest_in_all - 1)
|
930
|
+
@scroll_partial_screen = highest_in_all - @screen_height
|
931
|
+
else
|
932
|
+
if @scroll_partial_screen
|
933
|
+
if cursor_y <= @scroll_partial_screen
|
934
|
+
@scroll_partial_screen = cursor_y
|
935
|
+
elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
|
936
|
+
@scroll_partial_screen = cursor_y - (@screen_height - 1)
|
937
|
+
end
|
444
938
|
else
|
445
|
-
|
939
|
+
if cursor_y > (@screen_height - 1)
|
940
|
+
@scroll_partial_screen = cursor_y - (@screen_height - 1)
|
941
|
+
else
|
942
|
+
@scroll_partial_screen = 0
|
943
|
+
end
|
446
944
|
end
|
447
|
-
|
448
|
-
|
449
|
-
|
945
|
+
end
|
946
|
+
if @scroll_partial_screen != old_scroll_partial_screen
|
947
|
+
@rerender_all = true
|
948
|
+
end
|
949
|
+
else
|
950
|
+
if @scroll_partial_screen
|
951
|
+
@rerender_all = true
|
952
|
+
end
|
953
|
+
@scroll_partial_screen = nil
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
private def rerender_added_newline(prompt, prompt_width)
|
958
|
+
scroll_down(1)
|
959
|
+
@buffer_of_lines[@previous_line_index] = @line
|
960
|
+
@line = @buffer_of_lines[@line_index]
|
961
|
+
unless @in_pasting
|
962
|
+
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
|
963
|
+
end
|
964
|
+
@cursor = @cursor_max = calculate_width(@line)
|
965
|
+
@byte_pointer = @line.bytesize
|
966
|
+
@highest_in_all += @highest_in_this
|
967
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
968
|
+
@first_line_started_from += @started_from + 1
|
969
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
970
|
+
@previous_line_index = nil
|
971
|
+
end
|
972
|
+
|
973
|
+
def just_move_cursor
|
974
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
|
975
|
+
move_cursor_up(@started_from)
|
976
|
+
new_first_line_started_from =
|
977
|
+
if @line_index.zero?
|
978
|
+
0
|
979
|
+
else
|
980
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
981
|
+
end
|
982
|
+
first_line_diff = new_first_line_started_from - @first_line_started_from
|
983
|
+
new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
|
984
|
+
new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
|
985
|
+
calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
|
986
|
+
@previous_line_index = nil
|
987
|
+
if @rerender_all
|
988
|
+
@line = @buffer_of_lines[@line_index]
|
989
|
+
rerender_all_lines
|
450
990
|
@rerender_all = false
|
451
|
-
|
991
|
+
true
|
992
|
+
else
|
993
|
+
@line = @buffer_of_lines[@line_index]
|
994
|
+
@first_line_started_from = new_first_line_started_from
|
995
|
+
@started_from = new_started_from
|
996
|
+
@cursor = new_cursor
|
997
|
+
@cursor_max = new_cursor_max
|
998
|
+
@byte_pointer = new_byte_pointer
|
999
|
+
move_cursor_down(first_line_diff + @started_from)
|
1000
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
1001
|
+
false
|
452
1002
|
end
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
private def rerender_changed_current_line
|
1006
|
+
if @previous_line_index
|
1007
|
+
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
1008
|
+
else
|
1009
|
+
new_lines = whole_lines
|
1010
|
+
end
|
1011
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
1012
|
+
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
|
1013
|
+
diff = all_height - @highest_in_all
|
1014
|
+
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
|
1015
|
+
if diff > 0
|
1016
|
+
scroll_down(diff)
|
1017
|
+
move_cursor_up(all_height - 1)
|
1018
|
+
elsif diff < 0
|
1019
|
+
(-diff).times do
|
460
1020
|
Reline::IOGate.move_cursor_column(0)
|
461
1021
|
Reline::IOGate.erase_after_cursor
|
462
|
-
|
463
|
-
render_partial(prompt, prompt_width, line)
|
1022
|
+
move_cursor_up(1)
|
464
1023
|
end
|
1024
|
+
move_cursor_up(all_height - 1)
|
465
1025
|
else
|
466
|
-
|
467
|
-
|
1026
|
+
move_cursor_up(all_height - 1)
|
1027
|
+
end
|
1028
|
+
@highest_in_all = all_height
|
1029
|
+
back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
|
1030
|
+
move_cursor_up(back)
|
1031
|
+
if @previous_line_index
|
1032
|
+
@buffer_of_lines[@previous_line_index] = @line
|
1033
|
+
@line = @buffer_of_lines[@line_index]
|
1034
|
+
end
|
1035
|
+
@first_line_started_from =
|
1036
|
+
if @line_index.zero?
|
1037
|
+
0
|
1038
|
+
else
|
1039
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
1040
|
+
end
|
1041
|
+
if @prompt_proc
|
1042
|
+
prompt = prompt_list[@line_index]
|
1043
|
+
prompt_width = calculate_width(prompt, true)
|
1044
|
+
end
|
1045
|
+
move_cursor_down(@first_line_started_from)
|
1046
|
+
calculate_nearest_cursor
|
1047
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
1048
|
+
move_cursor_down(@started_from)
|
1049
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
1050
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
private def rerender_all_lines
|
1054
|
+
move_cursor_up(@first_line_started_from + @started_from)
|
1055
|
+
Reline::IOGate.move_cursor_column(0)
|
1056
|
+
back = 0
|
1057
|
+
new_buffer = whole_lines
|
1058
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
|
1059
|
+
new_buffer.each_with_index do |line, index|
|
1060
|
+
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
1061
|
+
width = prompt_width + calculate_width(line)
|
1062
|
+
height = calculate_height_by_width(width)
|
1063
|
+
back += height
|
1064
|
+
end
|
1065
|
+
old_highest_in_all = @highest_in_all
|
1066
|
+
if @line_index.zero?
|
1067
|
+
new_first_line_started_from = 0
|
1068
|
+
else
|
1069
|
+
new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
|
1070
|
+
end
|
1071
|
+
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
1072
|
+
calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
|
1073
|
+
if @scroll_partial_screen
|
1074
|
+
move_cursor_up(@first_line_started_from + @started_from)
|
1075
|
+
scroll_down(@screen_height - 1)
|
1076
|
+
move_cursor_up(@screen_height)
|
1077
|
+
Reline::IOGate.move_cursor_column(0)
|
1078
|
+
elsif back > old_highest_in_all
|
1079
|
+
scroll_down(back - 1)
|
1080
|
+
move_cursor_up(back - 1)
|
1081
|
+
elsif back < old_highest_in_all
|
1082
|
+
scroll_down(back)
|
1083
|
+
Reline::IOGate.erase_after_cursor
|
1084
|
+
(old_highest_in_all - back - 1).times do
|
468
1085
|
scroll_down(1)
|
469
|
-
Reline::IOGate.move_cursor_column(0)
|
470
1086
|
Reline::IOGate.erase_after_cursor
|
471
1087
|
end
|
1088
|
+
move_cursor_up(old_highest_in_all - 1)
|
1089
|
+
end
|
1090
|
+
render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
|
1091
|
+
if @prompt_proc
|
1092
|
+
prompt = prompt_list[@line_index]
|
1093
|
+
prompt_width = calculate_width(prompt, true)
|
1094
|
+
end
|
1095
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
1096
|
+
@highest_in_all = back
|
1097
|
+
@first_line_started_from = new_first_line_started_from
|
1098
|
+
@started_from = new_started_from
|
1099
|
+
if @scroll_partial_screen
|
1100
|
+
Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
|
1101
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
1102
|
+
else
|
1103
|
+
move_cursor_down(@first_line_started_from + @started_from - back + 1)
|
1104
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
472
1105
|
end
|
473
1106
|
end
|
474
1107
|
|
475
|
-
private def
|
1108
|
+
private def render_whole_lines(lines, prompt, prompt_width)
|
1109
|
+
rendered_height = 0
|
1110
|
+
modify_lines(lines).each_with_index do |line, index|
|
1111
|
+
if prompt.is_a?(Array)
|
1112
|
+
line_prompt = prompt[index]
|
1113
|
+
prompt_width = calculate_width(line_prompt, true)
|
1114
|
+
else
|
1115
|
+
line_prompt = prompt
|
1116
|
+
end
|
1117
|
+
height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
|
1118
|
+
if index < (lines.size - 1)
|
1119
|
+
if @scroll_partial_screen
|
1120
|
+
if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
|
1121
|
+
move_cursor_down(1)
|
1122
|
+
end
|
1123
|
+
else
|
1124
|
+
scroll_down(1)
|
1125
|
+
end
|
1126
|
+
rendered_height += height
|
1127
|
+
else
|
1128
|
+
rendered_height += height - 1
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
rendered_height
|
1132
|
+
end
|
1133
|
+
|
1134
|
+
private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
|
476
1135
|
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
|
1136
|
+
cursor_up_from_last_line = 0
|
1137
|
+
if @scroll_partial_screen
|
1138
|
+
last_visual_line = this_started_from + (height - 1)
|
1139
|
+
last_screen_line = @scroll_partial_screen + (@screen_height - 1)
|
1140
|
+
if (@scroll_partial_screen - this_started_from) >= height
|
1141
|
+
# Render nothing because this line is before the screen.
|
1142
|
+
visual_lines = []
|
1143
|
+
elsif this_started_from > last_screen_line
|
1144
|
+
# Render nothing because this line is after the screen.
|
1145
|
+
visual_lines = []
|
1146
|
+
else
|
1147
|
+
deleted_lines_before_screen = []
|
1148
|
+
if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
|
1149
|
+
# A part of visual lines are before the screen.
|
1150
|
+
deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
|
1151
|
+
deleted_lines_before_screen.compact!
|
1152
|
+
end
|
1153
|
+
if this_started_from <= last_screen_line and last_screen_line < last_visual_line
|
1154
|
+
# A part of visual lines are after the screen.
|
1155
|
+
visual_lines.pop((last_visual_line - last_screen_line) * 2)
|
1156
|
+
end
|
1157
|
+
move_cursor_up(deleted_lines_before_screen.size - @started_from)
|
1158
|
+
cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
|
1159
|
+
end
|
1160
|
+
end
|
477
1161
|
if with_control
|
478
1162
|
if height > @highest_in_this
|
479
1163
|
diff = height - @highest_in_this
|
@@ -488,13 +1172,17 @@ class Reline::LineEditor
|
|
488
1172
|
end
|
489
1173
|
move_cursor_up(@started_from)
|
490
1174
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
1175
|
+
cursor_up_from_last_line = height - 1 - @started_from
|
1176
|
+
end
|
1177
|
+
if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
|
1178
|
+
@output.write "\e[0m" # clear character decorations
|
491
1179
|
end
|
492
|
-
Reline::IOGate.move_cursor_column(0)
|
493
1180
|
visual_lines.each_with_index do |line, index|
|
1181
|
+
Reline::IOGate.move_cursor_column(0)
|
494
1182
|
if line.nil?
|
495
1183
|
if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
|
496
1184
|
# reaches the end of line
|
497
|
-
if Reline::IOGate.win?
|
1185
|
+
if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
|
498
1186
|
# A newline is automatically inserted if a character is rendered at
|
499
1187
|
# eol on command prompt.
|
500
1188
|
else
|
@@ -512,7 +1200,7 @@ class Reline::LineEditor
|
|
512
1200
|
next
|
513
1201
|
end
|
514
1202
|
@output.write line
|
515
|
-
if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
|
1203
|
+
if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
|
516
1204
|
# A newline is automatically inserted if a character is rendered at eol on command prompt.
|
517
1205
|
@rest_height -= 1 if @rest_height > 0
|
518
1206
|
end
|
@@ -522,14 +1210,18 @@ class Reline::LineEditor
|
|
522
1210
|
@pre_input_hook&.call
|
523
1211
|
end
|
524
1212
|
end
|
525
|
-
|
1213
|
+
unless visual_lines.empty?
|
1214
|
+
Reline::IOGate.erase_after_cursor
|
1215
|
+
Reline::IOGate.move_cursor_column(0)
|
1216
|
+
end
|
526
1217
|
if with_control
|
527
1218
|
# Just after rendring, so the cursor is on the last line.
|
528
1219
|
if finished?
|
529
1220
|
Reline::IOGate.move_cursor_column(0)
|
530
1221
|
else
|
531
1222
|
# Moves up from bottom of lines to the cursor position.
|
532
|
-
move_cursor_up(
|
1223
|
+
move_cursor_up(cursor_up_from_last_line)
|
1224
|
+
# This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
|
533
1225
|
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
534
1226
|
end
|
535
1227
|
end
|
@@ -537,7 +1229,7 @@ class Reline::LineEditor
|
|
537
1229
|
end
|
538
1230
|
|
539
1231
|
private def modify_lines(before)
|
540
|
-
return before if before.nil? || before.empty?
|
1232
|
+
return before if before.nil? || before.empty? || simplified_rendering?
|
541
1233
|
|
542
1234
|
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
543
1235
|
after.lines("\n").map { |l| l.chomp('') }
|
@@ -546,6 +1238,40 @@ class Reline::LineEditor
|
|
546
1238
|
end
|
547
1239
|
end
|
548
1240
|
|
1241
|
+
private def show_menu
|
1242
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
1243
|
+
@rerender_all = true
|
1244
|
+
@menu_info.list.sort!.each do |item|
|
1245
|
+
Reline::IOGate.move_cursor_column(0)
|
1246
|
+
@output.write item
|
1247
|
+
@output.flush
|
1248
|
+
scroll_down(1)
|
1249
|
+
end
|
1250
|
+
scroll_down(@highest_in_all - 1)
|
1251
|
+
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
private def clear_screen_buffer(prompt, prompt_list, prompt_width)
|
1255
|
+
Reline::IOGate.clear_screen
|
1256
|
+
back = 0
|
1257
|
+
modify_lines(whole_lines).each_with_index do |line, index|
|
1258
|
+
if @prompt_proc
|
1259
|
+
pr = prompt_list[index]
|
1260
|
+
height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
|
1261
|
+
else
|
1262
|
+
height = render_partial(prompt, prompt_width, line, back, with_control: false)
|
1263
|
+
end
|
1264
|
+
if index < (@buffer_of_lines.size - 1)
|
1265
|
+
move_cursor_down(1)
|
1266
|
+
back += height
|
1267
|
+
end
|
1268
|
+
end
|
1269
|
+
move_cursor_up(back)
|
1270
|
+
move_cursor_down(@first_line_started_from + @started_from)
|
1271
|
+
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
1272
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
1273
|
+
end
|
1274
|
+
|
549
1275
|
def editing_mode
|
550
1276
|
@config.editing_mode
|
551
1277
|
end
|
@@ -565,7 +1291,7 @@ class Reline::LineEditor
|
|
565
1291
|
else
|
566
1292
|
i&.start_with?(target)
|
567
1293
|
end
|
568
|
-
}
|
1294
|
+
}.uniq
|
569
1295
|
if is_menu
|
570
1296
|
menu(target, list)
|
571
1297
|
return nil
|
@@ -655,6 +1381,16 @@ class Reline::LineEditor
|
|
655
1381
|
@completion_journey_data = CompletionJourneyData.new(
|
656
1382
|
preposing, postposing,
|
657
1383
|
[target] + list.select{ |item| item.start_with?(target) }, 0)
|
1384
|
+
if @completion_journey_data.list.size == 1
|
1385
|
+
@completion_journey_data.pointer = 0
|
1386
|
+
else
|
1387
|
+
case direction
|
1388
|
+
when :up
|
1389
|
+
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
|
1390
|
+
when :down
|
1391
|
+
@completion_journey_data.pointer = 1
|
1392
|
+
end
|
1393
|
+
end
|
658
1394
|
@completion_state = CompletionState::JOURNEY
|
659
1395
|
else
|
660
1396
|
case direction
|
@@ -669,20 +1405,23 @@ class Reline::LineEditor
|
|
669
1405
|
@completion_journey_data.pointer = 0
|
670
1406
|
end
|
671
1407
|
end
|
672
|
-
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
673
|
-
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
674
|
-
line_to_pointer = @completion_journey_data.preposing + completed
|
675
|
-
@cursor_max = calculate_width(@line)
|
676
|
-
@cursor = calculate_width(line_to_pointer)
|
677
|
-
@byte_pointer = line_to_pointer.bytesize
|
678
1408
|
end
|
1409
|
+
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
1410
|
+
new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
|
1411
|
+
@line = new_line.nil? ? String.new(encoding: @encoding) : new_line
|
1412
|
+
line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
|
1413
|
+
line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
|
1414
|
+
@cursor_max = calculate_width(@line)
|
1415
|
+
@cursor = calculate_width(line_to_pointer)
|
1416
|
+
@byte_pointer = line_to_pointer.bytesize
|
679
1417
|
end
|
680
1418
|
|
681
1419
|
private def run_for_operators(key, method_symbol, &block)
|
682
1420
|
if @waiting_operator_proc
|
683
1421
|
if VI_MOTIONS.include?(method_symbol)
|
684
1422
|
old_cursor, old_byte_pointer = @cursor, @byte_pointer
|
685
|
-
|
1423
|
+
@vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
|
1424
|
+
block.(true)
|
686
1425
|
unless @waiting_proc
|
687
1426
|
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
|
688
1427
|
@cursor, @byte_pointer = old_cursor, old_byte_pointer
|
@@ -690,27 +1429,59 @@ class Reline::LineEditor
|
|
690
1429
|
else
|
691
1430
|
old_waiting_proc = @waiting_proc
|
692
1431
|
old_waiting_operator_proc = @waiting_operator_proc
|
1432
|
+
current_waiting_operator_proc = @waiting_operator_proc
|
693
1433
|
@waiting_proc = proc { |k|
|
694
1434
|
old_cursor, old_byte_pointer = @cursor, @byte_pointer
|
695
1435
|
old_waiting_proc.(k)
|
696
1436
|
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
|
697
1437
|
@cursor, @byte_pointer = old_cursor, old_byte_pointer
|
698
|
-
|
1438
|
+
current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
|
699
1439
|
@waiting_operator_proc = old_waiting_operator_proc
|
700
1440
|
}
|
701
1441
|
end
|
702
1442
|
else
|
703
1443
|
# Ignores operator when not motion is given.
|
704
|
-
block.()
|
1444
|
+
block.(false)
|
705
1445
|
end
|
706
1446
|
@waiting_operator_proc = nil
|
1447
|
+
@waiting_operator_vi_arg = nil
|
1448
|
+
if @vi_arg
|
1449
|
+
@rerender_all = true
|
1450
|
+
@vi_arg = nil
|
1451
|
+
end
|
707
1452
|
else
|
708
|
-
block.()
|
1453
|
+
block.(false)
|
709
1454
|
end
|
710
1455
|
end
|
711
1456
|
|
712
1457
|
private def argumentable?(method_obj)
|
713
|
-
method_obj and method_obj.parameters.
|
1458
|
+
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
private def inclusive?(method_obj)
|
1462
|
+
# If a motion method with the keyword argument "inclusive" follows the
|
1463
|
+
# operator, it must contain the character at the cursor position.
|
1464
|
+
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
|
1468
|
+
if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
|
1469
|
+
not_insertion = method_symbol != :ed_insert
|
1470
|
+
process_insert(force: not_insertion)
|
1471
|
+
end
|
1472
|
+
if @vi_arg and argumentable?(method_obj)
|
1473
|
+
if with_operator and inclusive?(method_obj)
|
1474
|
+
method_obj.(key, arg: @vi_arg, inclusive: true)
|
1475
|
+
else
|
1476
|
+
method_obj.(key, arg: @vi_arg)
|
1477
|
+
end
|
1478
|
+
else
|
1479
|
+
if with_operator and inclusive?(method_obj)
|
1480
|
+
method_obj.(key, inclusive: true)
|
1481
|
+
else
|
1482
|
+
method_obj.(key)
|
1483
|
+
end
|
1484
|
+
end
|
714
1485
|
end
|
715
1486
|
|
716
1487
|
private def process_key(key, method_symbol)
|
@@ -721,46 +1492,52 @@ class Reline::LineEditor
|
|
721
1492
|
end
|
722
1493
|
if method_symbol and key.is_a?(Symbol)
|
723
1494
|
if @vi_arg and argumentable?(method_obj)
|
724
|
-
run_for_operators(key, method_symbol) do
|
725
|
-
method_obj
|
1495
|
+
run_for_operators(key, method_symbol) do |with_operator|
|
1496
|
+
wrap_method_call(method_symbol, method_obj, key, with_operator)
|
726
1497
|
end
|
727
1498
|
else
|
728
|
-
method_obj
|
1499
|
+
wrap_method_call(method_symbol, method_obj, key) if method_obj
|
729
1500
|
end
|
730
1501
|
@kill_ring.process
|
731
|
-
@vi_arg
|
1502
|
+
if @vi_arg
|
1503
|
+
@rerender_al = true
|
1504
|
+
@vi_arg = nil
|
1505
|
+
end
|
732
1506
|
elsif @vi_arg
|
733
1507
|
if key.chr =~ /[0-9]/
|
734
1508
|
ed_argument_digit(key)
|
735
1509
|
else
|
736
1510
|
if argumentable?(method_obj)
|
737
|
-
run_for_operators(key, method_symbol) do
|
738
|
-
method_obj
|
1511
|
+
run_for_operators(key, method_symbol) do |with_operator|
|
1512
|
+
wrap_method_call(method_symbol, method_obj, key, with_operator)
|
739
1513
|
end
|
740
1514
|
elsif @waiting_proc
|
741
1515
|
@waiting_proc.(key)
|
742
1516
|
elsif method_obj
|
743
|
-
method_obj
|
1517
|
+
wrap_method_call(method_symbol, method_obj, key)
|
744
1518
|
else
|
745
|
-
ed_insert(key)
|
1519
|
+
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
|
746
1520
|
end
|
747
1521
|
@kill_ring.process
|
748
|
-
@vi_arg
|
1522
|
+
if @vi_arg
|
1523
|
+
@rerender_all = true
|
1524
|
+
@vi_arg = nil
|
1525
|
+
end
|
749
1526
|
end
|
750
1527
|
elsif @waiting_proc
|
751
1528
|
@waiting_proc.(key)
|
752
1529
|
@kill_ring.process
|
753
1530
|
elsif method_obj
|
754
1531
|
if method_symbol == :ed_argument_digit
|
755
|
-
method_obj
|
1532
|
+
wrap_method_call(method_symbol, method_obj, key)
|
756
1533
|
else
|
757
|
-
run_for_operators(key, method_symbol) do
|
758
|
-
method_obj
|
1534
|
+
run_for_operators(key, method_symbol) do |with_operator|
|
1535
|
+
wrap_method_call(method_symbol, method_obj, key, with_operator)
|
759
1536
|
end
|
760
1537
|
end
|
761
1538
|
@kill_ring.process
|
762
1539
|
else
|
763
|
-
ed_insert(key)
|
1540
|
+
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
|
764
1541
|
end
|
765
1542
|
end
|
766
1543
|
|
@@ -803,6 +1580,14 @@ class Reline::LineEditor
|
|
803
1580
|
end
|
804
1581
|
|
805
1582
|
def input_key(key)
|
1583
|
+
@last_key = key
|
1584
|
+
@config.reset_oneshot_key_bindings
|
1585
|
+
@dialogs.each do |dialog|
|
1586
|
+
if key.char.instance_of?(Symbol) and key.char == dialog.name
|
1587
|
+
return
|
1588
|
+
end
|
1589
|
+
end
|
1590
|
+
@just_cursor_moving = nil
|
806
1591
|
if key.char.nil?
|
807
1592
|
if @first_char
|
808
1593
|
@line = nil
|
@@ -810,6 +1595,7 @@ class Reline::LineEditor
|
|
810
1595
|
finish
|
811
1596
|
return
|
812
1597
|
end
|
1598
|
+
old_line = @line.dup
|
813
1599
|
@first_char = false
|
814
1600
|
completion_occurs = false
|
815
1601
|
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
|
@@ -817,7 +1603,21 @@ class Reline::LineEditor
|
|
817
1603
|
result = call_completion_proc
|
818
1604
|
if result.is_a?(Array)
|
819
1605
|
completion_occurs = true
|
820
|
-
|
1606
|
+
process_insert
|
1607
|
+
if @config.autocompletion
|
1608
|
+
move_completed_list(result, :down)
|
1609
|
+
else
|
1610
|
+
complete(result)
|
1611
|
+
end
|
1612
|
+
end
|
1613
|
+
end
|
1614
|
+
elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
|
1615
|
+
if not @config.disable_completion and @config.autocompletion
|
1616
|
+
result = call_completion_proc
|
1617
|
+
if result.is_a?(Array)
|
1618
|
+
completion_occurs = true
|
1619
|
+
process_insert
|
1620
|
+
move_completed_list(result, :up)
|
821
1621
|
end
|
822
1622
|
end
|
823
1623
|
elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
|
@@ -825,6 +1625,7 @@ class Reline::LineEditor
|
|
825
1625
|
result = call_completion_proc
|
826
1626
|
if result.is_a?(Array)
|
827
1627
|
completion_occurs = true
|
1628
|
+
process_insert
|
828
1629
|
move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
|
829
1630
|
end
|
830
1631
|
end
|
@@ -835,20 +1636,54 @@ class Reline::LineEditor
|
|
835
1636
|
end
|
836
1637
|
unless completion_occurs
|
837
1638
|
@completion_state = CompletionState::NORMAL
|
1639
|
+
@completion_journey_data = nil
|
1640
|
+
end
|
1641
|
+
if not @in_pasting and @just_cursor_moving.nil?
|
1642
|
+
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
1643
|
+
@just_cursor_moving = true
|
1644
|
+
elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
|
1645
|
+
@just_cursor_moving = true
|
1646
|
+
else
|
1647
|
+
@just_cursor_moving = false
|
1648
|
+
end
|
1649
|
+
else
|
1650
|
+
@just_cursor_moving = false
|
838
1651
|
end
|
839
|
-
if @is_multiline and @auto_indent_proc
|
1652
|
+
if @is_multiline and @auto_indent_proc and not simplified_rendering?
|
840
1653
|
process_auto_indent
|
841
1654
|
end
|
842
1655
|
end
|
843
1656
|
|
844
1657
|
def call_completion_proc
|
845
1658
|
result = retrieve_completion_block(true)
|
846
|
-
|
847
|
-
result =
|
1659
|
+
pre, target, post = result
|
1660
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
848
1661
|
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
849
1662
|
result
|
850
1663
|
end
|
851
1664
|
|
1665
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
1666
|
+
if @completion_proc and target
|
1667
|
+
argnum = @completion_proc.parameters.inject(0) { |result, item|
|
1668
|
+
case item.first
|
1669
|
+
when :req, :opt
|
1670
|
+
result + 1
|
1671
|
+
when :rest
|
1672
|
+
break 3
|
1673
|
+
end
|
1674
|
+
}
|
1675
|
+
case argnum
|
1676
|
+
when 1
|
1677
|
+
result = @completion_proc.(target)
|
1678
|
+
when 2
|
1679
|
+
result = @completion_proc.(target, pre)
|
1680
|
+
when 3..Float::INFINITY
|
1681
|
+
result = @completion_proc.(target, pre, post)
|
1682
|
+
end
|
1683
|
+
end
|
1684
|
+
result
|
1685
|
+
end
|
1686
|
+
|
852
1687
|
private def process_auto_indent
|
853
1688
|
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
|
854
1689
|
if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
|
@@ -874,6 +1709,7 @@ class Reline::LineEditor
|
|
874
1709
|
new_lines = whole_lines
|
875
1710
|
end
|
876
1711
|
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
|
1712
|
+
new_indent = @cursor_max if new_indent&.> @cursor_max
|
877
1713
|
if new_indent&.>= 0
|
878
1714
|
md = new_lines[@line_index].match(/\A */)
|
879
1715
|
prev_indent = md[0].count(' ')
|
@@ -891,8 +1727,16 @@ class Reline::LineEditor
|
|
891
1727
|
end
|
892
1728
|
|
893
1729
|
def retrieve_completion_block(set_completion_quote_character = false)
|
894
|
-
|
895
|
-
|
1730
|
+
if Reline.completer_word_break_characters.empty?
|
1731
|
+
word_break_regexp = nil
|
1732
|
+
else
|
1733
|
+
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
|
1734
|
+
end
|
1735
|
+
if Reline.completer_quote_characters.empty?
|
1736
|
+
quote_characters_regexp = nil
|
1737
|
+
else
|
1738
|
+
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
|
1739
|
+
end
|
896
1740
|
before = @line.byteslice(0, @byte_pointer)
|
897
1741
|
rest = nil
|
898
1742
|
break_pointer = nil
|
@@ -913,14 +1757,14 @@ class Reline::LineEditor
|
|
913
1757
|
elsif quote and slice.start_with?(escaped_quote)
|
914
1758
|
# skip
|
915
1759
|
i += 2
|
916
|
-
elsif slice =~ quote_characters_regexp # find new "
|
1760
|
+
elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
|
917
1761
|
rest = $'
|
918
1762
|
quote = $&
|
919
1763
|
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
|
920
1764
|
escaped_quote = /\\#{Regexp.escape(quote)}/
|
921
1765
|
i += 1
|
922
1766
|
break_pointer = i - 1
|
923
|
-
elsif not quote and slice =~ word_break_regexp
|
1767
|
+
elsif word_break_regexp and not quote and slice =~ word_break_regexp
|
924
1768
|
rest = $'
|
925
1769
|
i += 1
|
926
1770
|
before = @line.byteslice(i, @byte_pointer - i)
|
@@ -948,6 +1792,19 @@ class Reline::LineEditor
|
|
948
1792
|
end
|
949
1793
|
target = before
|
950
1794
|
end
|
1795
|
+
if @is_multiline
|
1796
|
+
if @previous_line_index
|
1797
|
+
lines = whole_lines(index: @previous_line_index, line: @line)
|
1798
|
+
else
|
1799
|
+
lines = whole_lines
|
1800
|
+
end
|
1801
|
+
if @line_index > 0
|
1802
|
+
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
|
1803
|
+
end
|
1804
|
+
if (lines.size - 1) > @line_index
|
1805
|
+
postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
|
1806
|
+
end
|
1807
|
+
end
|
951
1808
|
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
|
952
1809
|
end
|
953
1810
|
|
@@ -975,10 +1832,32 @@ class Reline::LineEditor
|
|
975
1832
|
|
976
1833
|
def delete_text(start = nil, length = nil)
|
977
1834
|
if start.nil? and length.nil?
|
978
|
-
@
|
979
|
-
|
980
|
-
|
981
|
-
|
1835
|
+
if @is_multiline
|
1836
|
+
if @buffer_of_lines.size == 1
|
1837
|
+
@line&.clear
|
1838
|
+
@byte_pointer = 0
|
1839
|
+
@cursor = 0
|
1840
|
+
@cursor_max = 0
|
1841
|
+
elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
|
1842
|
+
@buffer_of_lines.pop
|
1843
|
+
@line_index -= 1
|
1844
|
+
@line = @buffer_of_lines[@line_index]
|
1845
|
+
@byte_pointer = 0
|
1846
|
+
@cursor = 0
|
1847
|
+
@cursor_max = calculate_width(@line)
|
1848
|
+
elsif @line_index < (@buffer_of_lines.size - 1)
|
1849
|
+
@buffer_of_lines.delete_at(@line_index)
|
1850
|
+
@line = @buffer_of_lines[@line_index]
|
1851
|
+
@byte_pointer = 0
|
1852
|
+
@cursor = 0
|
1853
|
+
@cursor_max = calculate_width(@line)
|
1854
|
+
end
|
1855
|
+
else
|
1856
|
+
@line&.clear
|
1857
|
+
@byte_pointer = 0
|
1858
|
+
@cursor = 0
|
1859
|
+
@cursor_max = 0
|
1860
|
+
end
|
982
1861
|
elsif not start.nil? and not length.nil?
|
983
1862
|
if @line
|
984
1863
|
before = @line.byteslice(0, start)
|
@@ -1028,7 +1907,11 @@ class Reline::LineEditor
|
|
1028
1907
|
if @buffer_of_lines.size == 1 and @line.nil?
|
1029
1908
|
nil
|
1030
1909
|
else
|
1031
|
-
|
1910
|
+
if @previous_line_index
|
1911
|
+
whole_lines(index: @previous_line_index, line: @line).join("\n")
|
1912
|
+
else
|
1913
|
+
whole_lines.join("\n")
|
1914
|
+
end
|
1032
1915
|
end
|
1033
1916
|
end
|
1034
1917
|
|
@@ -1038,6 +1921,7 @@ class Reline::LineEditor
|
|
1038
1921
|
|
1039
1922
|
def finish
|
1040
1923
|
@finished = true
|
1924
|
+
@rerender_all = true
|
1041
1925
|
@config.reset
|
1042
1926
|
end
|
1043
1927
|
|
@@ -1066,48 +1950,98 @@ class Reline::LineEditor
|
|
1066
1950
|
|
1067
1951
|
private def key_newline(key)
|
1068
1952
|
if @is_multiline
|
1953
|
+
if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
|
1954
|
+
@add_newline_to_end_of_buffer = true
|
1955
|
+
end
|
1069
1956
|
next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
|
1070
1957
|
cursor_line = @line.byteslice(0, @byte_pointer)
|
1071
1958
|
insert_new_line(cursor_line, next_line)
|
1072
1959
|
@cursor = 0
|
1073
|
-
@check_new_auto_indent = true
|
1960
|
+
@check_new_auto_indent = true unless @in_pasting
|
1074
1961
|
end
|
1075
1962
|
end
|
1076
1963
|
|
1964
|
+
# Editline:: +ed-unassigned+ This editor command always results in an error.
|
1965
|
+
# GNU Readline:: There is no corresponding macro.
|
1077
1966
|
private def ed_unassigned(key) end # do nothing
|
1078
1967
|
|
1968
|
+
private def process_insert(force: false)
|
1969
|
+
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
|
1970
|
+
width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
|
1971
|
+
bytesize = @continuous_insertion_buffer.bytesize
|
1972
|
+
if @cursor == @cursor_max
|
1973
|
+
@line += @continuous_insertion_buffer
|
1974
|
+
else
|
1975
|
+
@line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
|
1976
|
+
end
|
1977
|
+
@byte_pointer += bytesize
|
1978
|
+
@cursor += width
|
1979
|
+
@cursor_max += width
|
1980
|
+
@continuous_insertion_buffer.clear
|
1981
|
+
end
|
1982
|
+
|
1983
|
+
# Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
|
1984
|
+
# In insert mode, insert the input character left of the cursor
|
1985
|
+
# position. In replace mode, overwrite the character at the
|
1986
|
+
# cursor and move the cursor to the right by one character
|
1987
|
+
# position. Accept an argument to do this repeatedly. It is an
|
1988
|
+
# error if the input character is the NUL character (+Ctrl-@+).
|
1989
|
+
# Failure to enlarge the edit buffer also results in an error.
|
1990
|
+
# Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
|
1991
|
+
# the input digit to the argument being read. Otherwise, call
|
1992
|
+
# +ed-insert+. It is an error if the input character is not a
|
1993
|
+
# digit or if the existing argument is already greater than a
|
1994
|
+
# million.
|
1995
|
+
# GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
|
1079
1996
|
private def ed_insert(key)
|
1997
|
+
str = nil
|
1998
|
+
width = nil
|
1999
|
+
bytesize = nil
|
1080
2000
|
if key.instance_of?(String)
|
1081
2001
|
begin
|
1082
2002
|
key.encode(Encoding::UTF_8)
|
1083
2003
|
rescue Encoding::UndefinedConversionError
|
1084
2004
|
return
|
1085
2005
|
end
|
1086
|
-
|
1087
|
-
|
1088
|
-
@line += key
|
1089
|
-
else
|
1090
|
-
@line = byteinsert(@line, @byte_pointer, key)
|
1091
|
-
end
|
1092
|
-
@byte_pointer += key.bytesize
|
1093
|
-
@cursor += width
|
1094
|
-
@cursor_max += width
|
2006
|
+
str = key
|
2007
|
+
bytesize = key.bytesize
|
1095
2008
|
else
|
1096
2009
|
begin
|
1097
2010
|
key.chr.encode(Encoding::UTF_8)
|
1098
2011
|
rescue Encoding::UndefinedConversionError
|
1099
2012
|
return
|
1100
2013
|
end
|
1101
|
-
|
1102
|
-
|
2014
|
+
str = key.chr
|
2015
|
+
bytesize = 1
|
2016
|
+
end
|
2017
|
+
if @in_pasting
|
2018
|
+
@continuous_insertion_buffer << str
|
2019
|
+
return
|
2020
|
+
elsif not @continuous_insertion_buffer.empty?
|
2021
|
+
process_insert
|
2022
|
+
end
|
2023
|
+
width = Reline::Unicode.get_mbchar_width(str)
|
2024
|
+
if @cursor == @cursor_max
|
2025
|
+
@line += str
|
2026
|
+
else
|
2027
|
+
@line = byteinsert(@line, @byte_pointer, str)
|
2028
|
+
end
|
2029
|
+
last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
2030
|
+
@byte_pointer += bytesize
|
2031
|
+
last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
|
2032
|
+
combined_char = last_mbchar + str
|
2033
|
+
if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
|
2034
|
+
# combined char
|
2035
|
+
last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
|
2036
|
+
combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
|
2037
|
+
if combined_char_width > last_mbchar_width
|
2038
|
+
width = combined_char_width - last_mbchar_width
|
1103
2039
|
else
|
1104
|
-
|
2040
|
+
width = 0
|
1105
2041
|
end
|
1106
|
-
width = Reline::Unicode.get_mbchar_width(key.chr)
|
1107
|
-
@byte_pointer += 1
|
1108
|
-
@cursor += width
|
1109
|
-
@cursor_max += width
|
1110
2042
|
end
|
2043
|
+
@cursor += width
|
2044
|
+
@cursor_max += width
|
1111
2045
|
end
|
1112
2046
|
alias_method :ed_digit, :ed_insert
|
1113
2047
|
alias_method :self_insert, :ed_insert
|
@@ -1117,6 +2051,8 @@ class Reline::LineEditor
|
|
1117
2051
|
arg.times do
|
1118
2052
|
if key == "\C-j".ord or key == "\C-m".ord
|
1119
2053
|
key_newline(key)
|
2054
|
+
elsif key == 0
|
2055
|
+
# Ignore NUL.
|
1120
2056
|
else
|
1121
2057
|
ed_insert(key)
|
1122
2058
|
end
|
@@ -1164,6 +2100,7 @@ class Reline::LineEditor
|
|
1164
2100
|
arg -= 1
|
1165
2101
|
ed_prev_char(key, arg: arg) if arg > 0
|
1166
2102
|
end
|
2103
|
+
alias_method :backward_char, :ed_prev_char
|
1167
2104
|
|
1168
2105
|
private def vi_first_print(key)
|
1169
2106
|
@byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
|
@@ -1316,9 +2253,11 @@ class Reline::LineEditor
|
|
1316
2253
|
searcher = generate_searcher
|
1317
2254
|
searcher.resume(key)
|
1318
2255
|
@searching_prompt = "(reverse-i-search)`': "
|
2256
|
+
termination_keys = ["\C-j".ord]
|
2257
|
+
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
|
1319
2258
|
@waiting_proc = ->(k) {
|
1320
2259
|
case k
|
1321
|
-
when
|
2260
|
+
when *termination_keys
|
1322
2261
|
if @history_pointer
|
1323
2262
|
buffer = Reline::HISTORY[@history_pointer]
|
1324
2263
|
else
|
@@ -1337,6 +2276,8 @@ class Reline::LineEditor
|
|
1337
2276
|
@waiting_proc = nil
|
1338
2277
|
@cursor_max = calculate_width(@line)
|
1339
2278
|
@cursor = @byte_pointer = 0
|
2279
|
+
@rerender_all = true
|
2280
|
+
@cached_prompt_list = nil
|
1340
2281
|
searcher.resume(-1)
|
1341
2282
|
when "\C-g".ord
|
1342
2283
|
if @is_multiline
|
@@ -1380,6 +2321,8 @@ class Reline::LineEditor
|
|
1380
2321
|
@waiting_proc = nil
|
1381
2322
|
@cursor_max = calculate_width(@line)
|
1382
2323
|
@cursor = @byte_pointer = 0
|
2324
|
+
@rerender_all = true
|
2325
|
+
@cached_prompt_list = nil
|
1383
2326
|
searcher.resume(-1)
|
1384
2327
|
end
|
1385
2328
|
end
|
@@ -1432,7 +2375,7 @@ class Reline::LineEditor
|
|
1432
2375
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1433
2376
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1434
2377
|
@line_index = line_no
|
1435
|
-
@line = @buffer_of_lines
|
2378
|
+
@line = @buffer_of_lines[@line_index]
|
1436
2379
|
@rerender_all = true
|
1437
2380
|
else
|
1438
2381
|
@line = Reline::HISTORY[@history_pointer]
|
@@ -1480,7 +2423,7 @@ class Reline::LineEditor
|
|
1480
2423
|
@line_index = line_no
|
1481
2424
|
end
|
1482
2425
|
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1483
|
-
@line = @buffer_of_lines
|
2426
|
+
@line = @buffer_of_lines[@line_index]
|
1484
2427
|
@rerender_all = true
|
1485
2428
|
else
|
1486
2429
|
if @history_pointer.nil? and substr.empty?
|
@@ -1544,6 +2487,7 @@ class Reline::LineEditor
|
|
1544
2487
|
arg -= 1
|
1545
2488
|
ed_prev_history(key, arg: arg) if arg > 0
|
1546
2489
|
end
|
2490
|
+
alias_method :previous_history, :ed_prev_history
|
1547
2491
|
|
1548
2492
|
private def ed_next_history(key, arg: 1)
|
1549
2493
|
if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
|
@@ -1591,8 +2535,10 @@ class Reline::LineEditor
|
|
1591
2535
|
arg -= 1
|
1592
2536
|
ed_next_history(key, arg: arg) if arg > 0
|
1593
2537
|
end
|
2538
|
+
alias_method :next_history, :ed_next_history
|
1594
2539
|
|
1595
2540
|
private def ed_newline(key)
|
2541
|
+
process_insert(force: true)
|
1596
2542
|
if @is_multiline
|
1597
2543
|
if @config.editing_mode_is?(:vi_command)
|
1598
2544
|
if @line_index < (@buffer_of_lines.size - 1)
|
@@ -1624,7 +2570,7 @@ class Reline::LineEditor
|
|
1624
2570
|
end
|
1625
2571
|
end
|
1626
2572
|
|
1627
|
-
private def em_delete_prev_char(key)
|
2573
|
+
private def em_delete_prev_char(key, arg: 1)
|
1628
2574
|
if @is_multiline and @cursor == 0 and @line_index > 0
|
1629
2575
|
@buffer_of_lines[@line_index] = @line
|
1630
2576
|
@cursor = calculate_width(@buffer_of_lines[@line_index - 1])
|
@@ -1642,9 +2588,16 @@ class Reline::LineEditor
|
|
1642
2588
|
@cursor -= width
|
1643
2589
|
@cursor_max -= width
|
1644
2590
|
end
|
2591
|
+
arg -= 1
|
2592
|
+
em_delete_prev_char(key, arg: arg) if arg > 0
|
1645
2593
|
end
|
1646
2594
|
alias_method :backward_delete_char, :em_delete_prev_char
|
1647
2595
|
|
2596
|
+
# Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
|
2597
|
+
# +Ctrl-U+) + Kill from the cursor to the end of the line.
|
2598
|
+
# GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
|
2599
|
+
# the line. With a negative numeric argument, kill backward
|
2600
|
+
# from the cursor to the beginning of the current line.
|
1648
2601
|
private def ed_kill_line(key)
|
1649
2602
|
if @line.bytesize > @byte_pointer
|
1650
2603
|
@line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
|
@@ -1661,8 +2614,14 @@ class Reline::LineEditor
|
|
1661
2614
|
@rest_height += 1
|
1662
2615
|
end
|
1663
2616
|
end
|
2617
|
+
alias_method :kill_line, :ed_kill_line
|
1664
2618
|
|
1665
|
-
|
2619
|
+
# Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
|
2620
|
+
# beginning of the edit buffer to the cursor and save it to the
|
2621
|
+
# cut buffer.
|
2622
|
+
# GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
|
2623
|
+
# to the beginning of the current line.
|
2624
|
+
private def vi_kill_line_prev(key)
|
1666
2625
|
if @byte_pointer > 0
|
1667
2626
|
@line, deleted = byteslice!(@line, 0, @byte_pointer)
|
1668
2627
|
@byte_pointer = 0
|
@@ -1671,6 +2630,22 @@ class Reline::LineEditor
|
|
1671
2630
|
@cursor = 0
|
1672
2631
|
end
|
1673
2632
|
end
|
2633
|
+
alias_method :unix_line_discard, :vi_kill_line_prev
|
2634
|
+
|
2635
|
+
# Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
|
2636
|
+
# edit buffer and save it to the cut buffer. +vi-kill-line-prev+
|
2637
|
+
# GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
|
2638
|
+
# current line, no matter where point is.
|
2639
|
+
private def em_kill_line(key)
|
2640
|
+
if @line.size > 0
|
2641
|
+
@kill_ring.append(@line.dup, true)
|
2642
|
+
@line.clear
|
2643
|
+
@byte_pointer = 0
|
2644
|
+
@cursor_max = 0
|
2645
|
+
@cursor = 0
|
2646
|
+
end
|
2647
|
+
end
|
2648
|
+
alias_method :kill_whole_line, :em_kill_line
|
1674
2649
|
|
1675
2650
|
private def em_delete(key)
|
1676
2651
|
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
|
@@ -1721,6 +2696,7 @@ class Reline::LineEditor
|
|
1721
2696
|
@byte_pointer += yanked.bytesize
|
1722
2697
|
end
|
1723
2698
|
end
|
2699
|
+
alias_method :yank, :em_yank
|
1724
2700
|
|
1725
2701
|
private def em_yank_pop(key)
|
1726
2702
|
yanked, prev_yank = @kill_ring.yank_pop
|
@@ -1737,6 +2713,7 @@ class Reline::LineEditor
|
|
1737
2713
|
@byte_pointer += yanked.bytesize
|
1738
2714
|
end
|
1739
2715
|
end
|
2716
|
+
alias_method :yank_pop, :em_yank_pop
|
1740
2717
|
|
1741
2718
|
private def ed_clear_screen(key)
|
1742
2719
|
@cleared = true
|
@@ -1867,9 +2844,10 @@ class Reline::LineEditor
|
|
1867
2844
|
@byte_pointer -= byte_size
|
1868
2845
|
@cursor -= width
|
1869
2846
|
@cursor_max -= width
|
1870
|
-
@kill_ring.append(deleted)
|
2847
|
+
@kill_ring.append(deleted, true)
|
1871
2848
|
end
|
1872
2849
|
end
|
2850
|
+
alias_method :unix_word_rubout, :em_kill_region
|
1873
2851
|
|
1874
2852
|
private def copy_for_vi(text)
|
1875
2853
|
if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
|
@@ -1890,11 +2868,11 @@ class Reline::LineEditor
|
|
1890
2868
|
ed_prev_char(key)
|
1891
2869
|
@config.editing_mode = :vi_command
|
1892
2870
|
end
|
1893
|
-
alias_method :
|
2871
|
+
alias_method :vi_movement_mode, :vi_command_mode
|
1894
2872
|
|
1895
2873
|
private def vi_next_word(key, arg: 1)
|
1896
2874
|
if @line.bytesize > @byte_pointer
|
1897
|
-
byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
|
2875
|
+
byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
|
1898
2876
|
@byte_pointer += byte_size
|
1899
2877
|
@cursor += width
|
1900
2878
|
end
|
@@ -1912,13 +2890,22 @@ class Reline::LineEditor
|
|
1912
2890
|
vi_prev_word(key, arg: arg) if arg > 0
|
1913
2891
|
end
|
1914
2892
|
|
1915
|
-
private def vi_end_word(key, arg: 1)
|
2893
|
+
private def vi_end_word(key, arg: 1, inclusive: false)
|
1916
2894
|
if @line.bytesize > @byte_pointer
|
1917
2895
|
byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
|
1918
2896
|
@byte_pointer += byte_size
|
1919
2897
|
@cursor += width
|
1920
2898
|
end
|
1921
2899
|
arg -= 1
|
2900
|
+
if inclusive and arg.zero?
|
2901
|
+
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
2902
|
+
if byte_size > 0
|
2903
|
+
c = @line.byteslice(@byte_pointer, byte_size)
|
2904
|
+
width = Reline::Unicode.get_mbchar_width(c)
|
2905
|
+
@byte_pointer += byte_size
|
2906
|
+
@cursor += width
|
2907
|
+
end
|
2908
|
+
end
|
1922
2909
|
vi_end_word(key, arg: arg) if arg > 0
|
1923
2910
|
end
|
1924
2911
|
|
@@ -1942,13 +2929,22 @@ class Reline::LineEditor
|
|
1942
2929
|
vi_prev_big_word(key, arg: arg) if arg > 0
|
1943
2930
|
end
|
1944
2931
|
|
1945
|
-
private def vi_end_big_word(key, arg: 1)
|
2932
|
+
private def vi_end_big_word(key, arg: 1, inclusive: false)
|
1946
2933
|
if @line.bytesize > @byte_pointer
|
1947
2934
|
byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
|
1948
2935
|
@byte_pointer += byte_size
|
1949
2936
|
@cursor += width
|
1950
2937
|
end
|
1951
2938
|
arg -= 1
|
2939
|
+
if inclusive and arg.zero?
|
2940
|
+
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
2941
|
+
if byte_size > 0
|
2942
|
+
c = @line.byteslice(@byte_pointer, byte_size)
|
2943
|
+
width = Reline::Unicode.get_mbchar_width(c)
|
2944
|
+
@byte_pointer += byte_size
|
2945
|
+
@cursor += width
|
2946
|
+
end
|
2947
|
+
end
|
1952
2948
|
vi_end_big_word(key, arg: arg) if arg > 0
|
1953
2949
|
end
|
1954
2950
|
|
@@ -2003,7 +2999,8 @@ class Reline::LineEditor
|
|
2003
2999
|
@cursor = 0
|
2004
3000
|
end
|
2005
3001
|
|
2006
|
-
private def vi_change_meta(key)
|
3002
|
+
private def vi_change_meta(key, arg: 1)
|
3003
|
+
@drop_terminate_spaces = true
|
2007
3004
|
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
2008
3005
|
if byte_pointer_diff > 0
|
2009
3006
|
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
|
@@ -2015,10 +3012,12 @@ class Reline::LineEditor
|
|
2015
3012
|
@cursor_max -= cursor_diff.abs
|
2016
3013
|
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
|
2017
3014
|
@config.editing_mode = :vi_insert
|
3015
|
+
@drop_terminate_spaces = false
|
2018
3016
|
}
|
3017
|
+
@waiting_operator_vi_arg = arg
|
2019
3018
|
end
|
2020
3019
|
|
2021
|
-
private def vi_delete_meta(key)
|
3020
|
+
private def vi_delete_meta(key, arg: 1)
|
2022
3021
|
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
2023
3022
|
if byte_pointer_diff > 0
|
2024
3023
|
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
|
@@ -2030,9 +3029,19 @@ class Reline::LineEditor
|
|
2030
3029
|
@cursor_max -= cursor_diff.abs
|
2031
3030
|
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
|
2032
3031
|
}
|
3032
|
+
@waiting_operator_vi_arg = arg
|
2033
3033
|
end
|
2034
3034
|
|
2035
|
-
private def vi_yank(key)
|
3035
|
+
private def vi_yank(key, arg: 1)
|
3036
|
+
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
3037
|
+
if byte_pointer_diff > 0
|
3038
|
+
cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
|
3039
|
+
elsif byte_pointer_diff < 0
|
3040
|
+
cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
|
3041
|
+
end
|
3042
|
+
copy_for_vi(cut)
|
3043
|
+
}
|
3044
|
+
@waiting_operator_vi_arg = arg
|
2036
3045
|
end
|
2037
3046
|
|
2038
3047
|
private def vi_list_or_eof(key)
|
@@ -2059,6 +3068,9 @@ class Reline::LineEditor
|
|
2059
3068
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2060
3069
|
@cursor_max -= width
|
2061
3070
|
if @cursor > 0 and @cursor >= @cursor_max
|
3071
|
+
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
3072
|
+
mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
|
3073
|
+
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2062
3074
|
@byte_pointer -= byte_size
|
2063
3075
|
@cursor -= width
|
2064
3076
|
end
|
@@ -2092,11 +3104,23 @@ class Reline::LineEditor
|
|
2092
3104
|
|
2093
3105
|
private def vi_histedit(key)
|
2094
3106
|
path = Tempfile.open { |fp|
|
2095
|
-
|
3107
|
+
if @is_multiline
|
3108
|
+
fp.write whole_lines.join("\n")
|
3109
|
+
else
|
3110
|
+
fp.write @line
|
3111
|
+
end
|
2096
3112
|
fp.path
|
2097
3113
|
}
|
2098
3114
|
system("#{ENV['EDITOR']} #{path}")
|
2099
|
-
@
|
3115
|
+
if @is_multiline
|
3116
|
+
@buffer_of_lines = File.read(path).split("\n")
|
3117
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
3118
|
+
@line_index = 0
|
3119
|
+
@line = @buffer_of_lines[@line_index]
|
3120
|
+
@rerender_all = true
|
3121
|
+
else
|
3122
|
+
@line = File.read(path)
|
3123
|
+
end
|
2100
3124
|
finish
|
2101
3125
|
end
|
2102
3126
|
|
@@ -2126,7 +3150,14 @@ class Reline::LineEditor
|
|
2126
3150
|
|
2127
3151
|
private def ed_argument_digit(key)
|
2128
3152
|
if @vi_arg.nil?
|
2129
|
-
|
3153
|
+
if key.chr.to_i.zero?
|
3154
|
+
if key.anybits?(0b10000000)
|
3155
|
+
unescaped_key = key ^ 0b10000000
|
3156
|
+
unless unescaped_key.chr.to_i.zero?
|
3157
|
+
@vi_arg = unescaped_key.chr.to_i
|
3158
|
+
end
|
3159
|
+
end
|
3160
|
+
else
|
2130
3161
|
@vi_arg = key.chr.to_i
|
2131
3162
|
end
|
2132
3163
|
else
|
@@ -2155,7 +3186,7 @@ class Reline::LineEditor
|
|
2155
3186
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
2156
3187
|
before = @line.byteslice(0, @byte_pointer)
|
2157
3188
|
remaining_point = @byte_pointer + byte_size
|
2158
|
-
after = @line.byteslice(remaining_point, @line.
|
3189
|
+
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
|
2159
3190
|
@line = before + k.chr + after
|
2160
3191
|
@cursor_max = calculate_width(@line)
|
2161
3192
|
@waiting_proc = nil
|
@@ -2166,7 +3197,7 @@ class Reline::LineEditor
|
|
2166
3197
|
end
|
2167
3198
|
before = @line.byteslice(0, @byte_pointer)
|
2168
3199
|
remaining_point = @byte_pointer + byte_size
|
2169
|
-
after = @line.byteslice(remaining_point, @line.
|
3200
|
+
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
|
2170
3201
|
replaced = k.chr * arg
|
2171
3202
|
@line = before + replaced + after
|
2172
3203
|
@byte_pointer += replaced.bytesize
|
@@ -2177,15 +3208,15 @@ class Reline::LineEditor
|
|
2177
3208
|
}
|
2178
3209
|
end
|
2179
3210
|
|
2180
|
-
private def vi_next_char(key, arg: 1)
|
2181
|
-
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
|
3211
|
+
private def vi_next_char(key, arg: 1, inclusive: false)
|
3212
|
+
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
|
2182
3213
|
end
|
2183
3214
|
|
2184
|
-
private def vi_to_next_char(key, arg: 1)
|
2185
|
-
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
|
3215
|
+
private def vi_to_next_char(key, arg: 1, inclusive: false)
|
3216
|
+
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
|
2186
3217
|
end
|
2187
3218
|
|
2188
|
-
private def search_next_char(key, arg, need_prev_char
|
3219
|
+
private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
|
2189
3220
|
if key.instance_of?(String)
|
2190
3221
|
inputed_char = key
|
2191
3222
|
else
|
@@ -2222,6 +3253,15 @@ class Reline::LineEditor
|
|
2222
3253
|
@byte_pointer += byte_size
|
2223
3254
|
@cursor += width
|
2224
3255
|
end
|
3256
|
+
if inclusive
|
3257
|
+
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
3258
|
+
if byte_size > 0
|
3259
|
+
c = @line.byteslice(@byte_pointer, byte_size)
|
3260
|
+
width = Reline::Unicode.get_mbchar_width(c)
|
3261
|
+
@byte_pointer += byte_size
|
3262
|
+
@cursor += width
|
3263
|
+
end
|
3264
|
+
end
|
2225
3265
|
@waiting_proc = nil
|
2226
3266
|
end
|
2227
3267
|
|
@@ -2293,6 +3333,7 @@ class Reline::LineEditor
|
|
2293
3333
|
alias_method :set_mark, :em_set_mark
|
2294
3334
|
|
2295
3335
|
private def em_exchange_mark(key)
|
3336
|
+
return unless @mark_pointer
|
2296
3337
|
new_pointer = [@byte_pointer, @line_index]
|
2297
3338
|
@previous_line_index = @line_index
|
2298
3339
|
@byte_pointer, @line_index = @mark_pointer
|