reline 0.1.5 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb0a1da0d5f51d50b82a78a3084a69ca37821b541e3c362bcb2b9a10a04664a3
4
- data.tar.gz: bc1860cf76efa34984aef1dbafb60d1f7781b86ba44a9392dc5fbd8025631121
3
+ metadata.gz: 99b4f2cf521a3b774a0c763517fc373fdc2bf1603e64ab8e853513ed64946074
4
+ data.tar.gz: 4665d8f6c3db58c573186b4fb245478a1190fdb5da5f9c5f0d569bcfb2cba2f4
5
5
  SHA512:
6
- metadata.gz: 4691bf1855429c5f1c25b16f66bc1e4793290579ae9a7345d1dd97c2694863bec59bab30edb232665b38a1f302a4e601c99111c84e5c277651ab2e53d4353982
7
- data.tar.gz: 9ba7b27a82ce1d823f394c78ba1d995b6c4f71ddecb13c17c27fb39a13b7503c3b84402feb0239eb33d19d87f00d06c0608e35439413f6c72d2bdea50e84d10e
6
+ metadata.gz: 7b66388f8b9008f5008902e662133312006263622cf7d12d458f5611446b536baf7ff66197209e7b82ada8d34d1b0c8d6eb40c6be35315134c289ded8cc421f7
7
+ data.tar.gz: 74ce2b03a99eb548c25050f606140a9fd62351e2cbe20037977f416cc20153f3d711e946c84c4857e66b078a33b7dd12a359f429a2c31091ece7e8f776204931
@@ -44,6 +44,7 @@ module Reline
44
44
  self.output = STDOUT
45
45
  yield self
46
46
  @completion_quote_character = nil
47
+ @bracketed_paste_finished = false
47
48
  end
48
49
 
49
50
  def encoding
@@ -199,7 +200,11 @@ module Reline
199
200
 
200
201
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
201
202
  if ENV['RELINE_STDERR_TTY']
202
- $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
203
+ if Reline::IOGate.win?
204
+ $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
205
+ else
206
+ $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
207
+ end
203
208
  $stderr.sync = true
204
209
  $stderr.puts "Reline is used by #{Process.pid}"
205
210
  end
@@ -235,13 +240,23 @@ module Reline
235
240
  line_editor.rerender
236
241
 
237
242
  begin
243
+ prev_pasting_state = false
238
244
  loop do
245
+ prev_pasting_state = Reline::IOGate.in_pasting?
239
246
  read_io(config.keyseq_timeout) { |inputs|
240
247
  inputs.each { |c|
241
248
  line_editor.input_key(c)
242
249
  line_editor.rerender
243
250
  }
251
+ if @bracketed_paste_finished
252
+ line_editor.rerender_all
253
+ @bracketed_paste_finished = false
254
+ end
244
255
  }
256
+ if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
257
+ prev_pasting_state = false
258
+ line_editor.rerender_all
259
+ end
245
260
  break if line_editor.finished?
246
261
  end
247
262
  Reline::IOGate.move_cursor_column(0)
@@ -269,8 +284,13 @@ module Reline
269
284
  buffer = []
270
285
  loop do
271
286
  c = Reline::IOGate.getc
272
- buffer << c
273
- result = key_stroke.match_status(buffer)
287
+ if c == -1
288
+ result = :unmatched
289
+ @bracketed_paste_finished = true
290
+ else
291
+ buffer << c
292
+ result = key_stroke.match_status(buffer)
293
+ end
274
294
  case result
275
295
  when :matched
276
296
  expanded = key_stroke.expand(buffer).map{ |expanded_c|
@@ -1,4 +1,5 @@
1
1
  require 'io/console'
2
+ require 'timeout'
2
3
 
3
4
  class Reline::ANSI
4
5
  def self.encoding
@@ -67,7 +68,7 @@ class Reline::ANSI
67
68
  end
68
69
 
69
70
  @@buf = []
70
- def self.getc
71
+ def self.inner_getc
71
72
  unless @@buf.empty?
72
73
  return @@buf.shift
73
74
  end
@@ -80,6 +81,62 @@ class Reline::ANSI
80
81
  nil
81
82
  end
82
83
 
84
+ @@in_bracketed_paste_mode = false
85
+ START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
86
+ END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
87
+ def self.getc_with_bracketed_paste
88
+ buffer = String.new(encoding: Encoding::ASCII_8BIT)
89
+ buffer << inner_getc
90
+ while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
91
+ if START_BRACKETED_PASTE == buffer
92
+ @@in_bracketed_paste_mode = true
93
+ return inner_getc
94
+ elsif END_BRACKETED_PASTE == buffer
95
+ @@in_bracketed_paste_mode = false
96
+ ungetc(-1)
97
+ return inner_getc
98
+ end
99
+ begin
100
+ succ_c = nil
101
+ Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
102
+ succ_c = inner_getc
103
+ }
104
+ rescue Timeout::Error
105
+ break
106
+ else
107
+ buffer << succ_c
108
+ end
109
+ end
110
+ buffer.bytes.reverse_each do |ch|
111
+ ungetc ch
112
+ end
113
+ inner_getc
114
+ end
115
+
116
+ def self.getc
117
+ if Reline.core.config.enable_bracketed_paste
118
+ getc_with_bracketed_paste
119
+ else
120
+ inner_getc
121
+ end
122
+ end
123
+
124
+ def self.in_pasting?
125
+ @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
126
+ end
127
+
128
+ def self.empty_buffer?
129
+ unless @@buf.empty?
130
+ return false
131
+ end
132
+ rs, = IO.select([@@input], [], [], 0.00001)
133
+ if rs and rs[0]
134
+ false
135
+ else
136
+ true
137
+ end
138
+ end
139
+
83
140
  def self.ungetc(c)
84
141
  @@buf.unshift(c)
85
142
  end
@@ -115,7 +172,7 @@ class Reline::ANSI
115
172
 
116
173
  def self.cursor_pos
117
174
  begin
118
- res = ''
175
+ res = +''
119
176
  m = nil
120
177
  @@input.raw do |stdin|
121
178
  @@output << "\e[6n"
@@ -37,6 +37,7 @@ class Reline::Config
37
37
  vi-cmd-mode-icon
38
38
  vi-ins-mode-icon
39
39
  emacs-mode-string
40
+ enable-bracketed-paste
40
41
  }
41
42
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
42
43
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -1,6 +1,10 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
+ def self.reset
5
+ @@pasting = false
6
+ end
7
+
4
8
  def self.encoding
5
9
  RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
6
10
  end
@@ -67,6 +71,20 @@ class Reline::GeneralIO
67
71
  def self.set_winch_handler(&handler)
68
72
  end
69
73
 
74
+ @@pasting = false
75
+
76
+ def self.in_pasting?
77
+ @@pasting
78
+ end
79
+
80
+ def self.start_pasting
81
+ @@pasting = true
82
+ end
83
+
84
+ def self.finish_pasting
85
+ @@pasting = false
86
+ end
87
+
70
88
  def self.prep
71
89
  end
72
90
 
@@ -307,7 +307,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
307
307
  # 152 M-^X
308
308
  :ed_unassigned,
309
309
  # 153 M-^Y
310
- :ed_unassigned,
310
+ :em_yank_pop,
311
311
  # 154 M-^Z
312
312
  :ed_unassigned,
313
313
  # 155 M-^[
@@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
17
17
  # 7 ^G
18
18
  :ed_unassigned,
19
19
  # 8 ^H
20
- :ed_delete_prev_char,
20
+ :ed_unassigned,
21
21
  # 9 ^I
22
22
  :ed_unassigned,
23
23
  # 10 ^J
@@ -255,7 +255,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
255
255
  # 126 ~
256
256
  :vi_change_case,
257
257
  # 127 ^?
258
- :ed_delete_prev_char,
258
+ :ed_unassigned,
259
259
  # 128 M-^@
260
260
  :ed_unassigned,
261
261
  # 129 M-^A
@@ -1,4 +1,6 @@
1
1
  class Reline::KillRing
2
+ include Enumerable
3
+
2
4
  module State
3
5
  FRESH = :fresh
4
6
  CONTINUED = :continued
@@ -110,4 +112,14 @@ class Reline::KillRing
110
112
  nil
111
113
  end
112
114
  end
115
+
116
+ def each
117
+ start = head = @ring.head
118
+ loop do
119
+ break if head.nil?
120
+ yield head.str
121
+ head = head.backward
122
+ break if head == start
123
+ end
124
+ end
113
125
  end
@@ -50,12 +50,24 @@ class Reline::LineEditor
50
50
  CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
51
51
  MenuInfo = Struct.new('MenuInfo', :target, :list)
52
52
 
53
+ PROMPT_LIST_CACHE_TIMEOUT = 0.5
54
+
53
55
  def initialize(config, encoding)
54
56
  @config = config
55
57
  @completion_append_character = ''
56
58
  reset_variables(encoding: encoding)
57
59
  end
58
60
 
61
+ def simplified_rendering?
62
+ if finished?
63
+ false
64
+ elsif @just_cursor_moving and not @rerender_all
65
+ true
66
+ else
67
+ not @rerender_all and not finished? and Reline::IOGate.in_pasting?
68
+ end
69
+ end
70
+
59
71
  private def check_multiline_prompt(buffer, prompt)
60
72
  if @vi_arg
61
73
  prompt = "(arg: #{@vi_arg}) "
@@ -66,8 +78,22 @@ class Reline::LineEditor
66
78
  else
67
79
  prompt = @prompt
68
80
  end
81
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
69
82
  if @prompt_proc
70
- prompt_list = @prompt_proc.(buffer)
83
+ use_cached_prompt_list = false
84
+ if @cached_prompt_list
85
+ if @just_cursor_moving
86
+ use_cached_prompt_list = true
87
+ elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
88
+ use_cached_prompt_list = true
89
+ end
90
+ end
91
+ if use_cached_prompt_list
92
+ prompt_list = @cached_prompt_list
93
+ else
94
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
95
+ @prompt_cache_time = Time.now.to_f
96
+ end
71
97
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
72
98
  if @config.show_mode_in_prompt
73
99
  if @config.editing_mode_is?(:vi_command)
@@ -105,6 +131,7 @@ class Reline::LineEditor
105
131
  def reset(prompt = '', encoding:)
106
132
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
107
133
  @screen_size = Reline::IOGate.get_screen_size
134
+ @screen_height = @screen_size.first
108
135
  reset_variables(prompt, encoding: encoding)
109
136
  @old_trap = Signal.trap('SIGINT') {
110
137
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
@@ -114,6 +141,7 @@ class Reline::LineEditor
114
141
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
115
142
  old_screen_size = @screen_size
116
143
  @screen_size = Reline::IOGate.get_screen_size
144
+ @screen_height = @screen_size.first
117
145
  if old_screen_size.last < @screen_size.last # columns increase
118
146
  @rerender_all = true
119
147
  rerender
@@ -165,11 +193,12 @@ class Reline::LineEditor
165
193
  @cleared = false
166
194
  @rerender_all = false
167
195
  @history_pointer = nil
168
- @kill_ring = Reline::KillRing.new
196
+ @kill_ring ||= Reline::KillRing.new
169
197
  @vi_clipboard = ''
170
198
  @vi_arg = nil
171
199
  @waiting_proc = nil
172
200
  @waiting_operator_proc = nil
201
+ @waiting_operator_vi_arg = nil
173
202
  @completion_journey_data = nil
174
203
  @completion_state = CompletionState::NORMAL
175
204
  @perfect_matched = nil
@@ -177,7 +206,13 @@ class Reline::LineEditor
177
206
  @first_prompt = true
178
207
  @searching_prompt = nil
179
208
  @first_char = true
209
+ @add_newline_to_end_of_buffer = false
210
+ @just_cursor_moving = nil
211
+ @cached_prompt_list = nil
212
+ @prompt_cache_time = nil
180
213
  @eof = false
214
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
215
+ @scroll_partial_screen = nil
181
216
  reset_line
182
217
  end
183
218
 
@@ -222,6 +257,7 @@ class Reline::LineEditor
222
257
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
223
258
  @previous_line_index = @line_index
224
259
  @line_index += 1
260
+ @just_cursor_moving = false
225
261
  end
226
262
 
227
263
  private def calculate_height_by_width(width)
@@ -262,28 +298,28 @@ class Reline::LineEditor
262
298
  end
263
299
  end
264
300
 
265
- private def calculate_nearest_cursor
266
- @cursor_max = calculate_width(line)
301
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
302
+ new_cursor_max = calculate_width(line_to_calc)
267
303
  new_cursor = 0
268
304
  new_byte_pointer = 0
269
305
  height = 1
270
306
  max_width = @screen_size.last
271
307
  if @config.editing_mode_is?(:vi_command)
272
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
308
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
273
309
  if last_byte_size > 0
274
- last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
310
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
275
311
  last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
276
- cursor_max = @cursor_max - last_width
312
+ end_of_line_cursor = new_cursor_max - last_width
277
313
  else
278
- cursor_max = @cursor_max
314
+ end_of_line_cursor = new_cursor_max
279
315
  end
280
316
  else
281
- cursor_max = @cursor_max
317
+ end_of_line_cursor = new_cursor_max
282
318
  end
283
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
319
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
284
320
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
285
321
  now = new_cursor + mbchar_width
286
- if now > cursor_max or now > @cursor
322
+ if now > end_of_line_cursor or now > cursor
287
323
  break
288
324
  end
289
325
  new_cursor += mbchar_width
@@ -292,9 +328,21 @@ class Reline::LineEditor
292
328
  end
293
329
  new_byte_pointer += gc.bytesize
294
330
  end
295
- @started_from = height - 1
296
- @cursor = new_cursor
297
- @byte_pointer = new_byte_pointer
331
+ new_started_from = height - 1
332
+ if update
333
+ @cursor = new_cursor
334
+ @cursor_max = new_cursor_max
335
+ @started_from = new_started_from
336
+ @byte_pointer = new_byte_pointer
337
+ else
338
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
339
+ end
340
+ end
341
+
342
+ def rerender_all
343
+ @rerender_all = true
344
+ process_insert(force: true)
345
+ rerender
298
346
  end
299
347
 
300
348
  def rerender
@@ -302,168 +350,53 @@ class Reline::LineEditor
302
350
  if @menu_info
303
351
  scroll_down(@highest_in_all - @first_line_started_from)
304
352
  @rerender_all = true
305
- @menu_info.list.sort!.each do |item|
306
- Reline::IOGate.move_cursor_column(0)
307
- @output.write item
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)
353
+ end
354
+ if @menu_info
355
+ show_menu
313
356
  @menu_info = nil
314
357
  end
315
358
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
316
359
  if @cleared
317
- Reline::IOGate.clear_screen
360
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
318
361
  @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
362
  return
336
363
  end
337
364
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
338
365
  # FIXME: end of logical line sometimes breaks
339
- if @previous_line_index or new_highest_in_this != @highest_in_this
340
- if @previous_line_index
341
- new_lines = whole_lines(index: @previous_line_index, line: @line)
342
- else
343
- new_lines = whole_lines
344
- end
345
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
346
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
347
- diff = all_height - @highest_in_all
348
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
349
- if diff > 0
350
- scroll_down(diff)
351
- move_cursor_up(all_height - 1)
352
- elsif diff < 0
353
- (-diff).times do
354
- Reline::IOGate.move_cursor_column(0)
355
- Reline::IOGate.erase_after_cursor
356
- move_cursor_up(1)
357
- end
358
- move_cursor_up(all_height - 1)
366
+ if @add_newline_to_end_of_buffer
367
+ rerender_added_newline
368
+ @add_newline_to_end_of_buffer = false
369
+ else
370
+ if @just_cursor_moving and not @rerender_all
371
+ rendered = just_move_cursor
372
+ @just_cursor_moving = false
373
+ return
374
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
375
+ rerender_changed_current_line
376
+ @previous_line_index = nil
377
+ rendered = true
378
+ elsif @rerender_all
379
+ rerender_all_lines
380
+ @rerender_all = false
381
+ rendered = true
359
382
  else
360
- move_cursor_up(all_height - 1)
361
- end
362
- @highest_in_all = all_height
363
- back = 0
364
- modify_lines(new_lines).each_with_index do |line, index|
365
- if @prompt_proc
366
- prompt = prompt_list[index]
367
- prompt_width = calculate_width(prompt, true)
368
- end
369
- height = render_partial(prompt, prompt_width, line, false)
370
- if index < (new_lines.size - 1)
371
- scroll_down(1)
372
- back += height
373
- else
374
- back += height - 1
375
- end
376
- end
377
- move_cursor_up(back)
378
- if @previous_line_index
379
- @buffer_of_lines[@previous_line_index] = @line
380
- @line = @buffer_of_lines[@line_index]
381
- end
382
- @first_line_started_from =
383
- if @line_index.zero?
384
- 0
385
- else
386
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
387
- end
388
- if @prompt_proc
389
- prompt = prompt_list[@line_index]
390
- prompt_width = calculate_width(prompt, true)
391
- end
392
- move_cursor_down(@first_line_started_from)
393
- calculate_nearest_cursor
394
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
395
- move_cursor_down(@started_from)
396
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
397
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
398
- @previous_line_index = nil
399
- rendered = true
400
- elsif @rerender_all
401
- move_cursor_up(@first_line_started_from + @started_from)
402
- Reline::IOGate.move_cursor_column(0)
403
- back = 0
404
- new_buffer = whole_lines
405
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
406
- new_buffer.each_with_index do |line, index|
407
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
408
- width = prompt_width + calculate_width(line)
409
- height = calculate_height_by_width(width)
410
- back += height
411
- end
412
- if back > @highest_in_all
413
- scroll_down(back - 1)
414
- move_cursor_up(back - 1)
415
- elsif back < @highest_in_all
416
- scroll_down(back)
417
- Reline::IOGate.erase_after_cursor
418
- (@highest_in_all - back - 1).times do
419
- scroll_down(1)
420
- Reline::IOGate.erase_after_cursor
421
- end
422
- move_cursor_up(@highest_in_all - 1)
423
- end
424
- modify_lines(new_buffer).each_with_index do |line, index|
425
- if @prompt_proc
426
- prompt = prompt_list[index]
427
- prompt_width = calculate_width(prompt, true)
428
- end
429
- render_partial(prompt, prompt_width, line, false)
430
- if index < (new_buffer.size - 1)
431
- move_cursor_down(1)
432
- end
433
383
  end
434
- move_cursor_up(back - 1)
435
- if @prompt_proc
436
- prompt = prompt_list[@line_index]
437
- prompt_width = calculate_width(prompt, true)
438
- end
439
- @highest_in_all = back
440
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
441
- @first_line_started_from =
442
- if @line_index.zero?
443
- 0
444
- else
445
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
446
- end
447
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
448
- move_cursor_down(@first_line_started_from + @started_from)
449
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
450
- @rerender_all = false
451
- rendered = true
452
384
  end
453
385
  line = modify_lines(whole_lines)[@line_index]
454
386
  if @is_multiline
455
387
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
456
388
  if finished?
457
389
  # Always rerender on finish because output_modifier_proc may return a different output.
458
- render_partial(prompt, prompt_width, line)
390
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
459
391
  scroll_down(1)
460
392
  Reline::IOGate.move_cursor_column(0)
461
393
  Reline::IOGate.erase_after_cursor
462
394
  elsif not rendered
463
- render_partial(prompt, prompt_width, line)
395
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
464
396
  end
397
+ @buffer_of_lines[@line_index] = @line
465
398
  else
466
- render_partial(prompt, prompt_width, line)
399
+ render_partial(prompt, prompt_width, line, 0)
467
400
  if finished?
468
401
  scroll_down(1)
469
402
  Reline::IOGate.move_cursor_column(0)
@@ -472,8 +405,237 @@ class Reline::LineEditor
472
405
  end
473
406
  end
474
407
 
475
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
408
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
409
+ if @screen_height < highest_in_all
410
+ old_scroll_partial_screen = @scroll_partial_screen
411
+ if cursor_y == 0
412
+ @scroll_partial_screen = 0
413
+ elsif cursor_y == (highest_in_all - 1)
414
+ @scroll_partial_screen = highest_in_all - @screen_height
415
+ else
416
+ if @scroll_partial_screen
417
+ if cursor_y <= @scroll_partial_screen
418
+ @scroll_partial_screen = cursor_y
419
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
420
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
421
+ end
422
+ else
423
+ if cursor_y > (@screen_height - 1)
424
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
425
+ else
426
+ @scroll_partial_screen = 0
427
+ end
428
+ end
429
+ end
430
+ if @scroll_partial_screen != old_scroll_partial_screen
431
+ @rerender_all = true
432
+ end
433
+ else
434
+ if @scroll_partial_screen
435
+ @rerender_all = true
436
+ end
437
+ @scroll_partial_screen = nil
438
+ end
439
+ end
440
+
441
+ private def rerender_added_newline
442
+ scroll_down(1)
443
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
444
+ prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
445
+ @buffer_of_lines[@previous_line_index] = @line
446
+ @line = @buffer_of_lines[@line_index]
447
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
448
+ @cursor = @cursor_max = calculate_width(@line)
449
+ @byte_pointer = @line.bytesize
450
+ @highest_in_all += @highest_in_this
451
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
452
+ @first_line_started_from += @started_from + 1
453
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
454
+ @previous_line_index = nil
455
+ end
456
+
457
+ def just_move_cursor
458
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
459
+ move_cursor_up(@started_from)
460
+ new_first_line_started_from =
461
+ if @line_index.zero?
462
+ 0
463
+ else
464
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
465
+ end
466
+ first_line_diff = new_first_line_started_from - @first_line_started_from
467
+ new_cursor, _, new_started_from, _ = calculate_nearest_cursor(@line, @cursor, @started_from, @byte_pointer, false)
468
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
469
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
470
+ @previous_line_index = nil
471
+ if @rerender_all
472
+ @line = @buffer_of_lines[@line_index]
473
+ rerender_all_lines
474
+ @rerender_all = false
475
+ true
476
+ else
477
+ @line = @buffer_of_lines[@line_index]
478
+ @first_line_started_from = new_first_line_started_from
479
+ @started_from = new_started_from
480
+ @cursor = new_cursor
481
+ move_cursor_down(first_line_diff + @started_from)
482
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
483
+ false
484
+ end
485
+ end
486
+
487
+ private def rerender_changed_current_line
488
+ if @previous_line_index
489
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
490
+ else
491
+ new_lines = whole_lines
492
+ end
493
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
494
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
495
+ diff = all_height - @highest_in_all
496
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
497
+ if diff > 0
498
+ scroll_down(diff)
499
+ move_cursor_up(all_height - 1)
500
+ elsif diff < 0
501
+ (-diff).times do
502
+ Reline::IOGate.move_cursor_column(0)
503
+ Reline::IOGate.erase_after_cursor
504
+ move_cursor_up(1)
505
+ end
506
+ move_cursor_up(all_height - 1)
507
+ else
508
+ move_cursor_up(all_height - 1)
509
+ end
510
+ @highest_in_all = all_height
511
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
512
+ move_cursor_up(back)
513
+ if @previous_line_index
514
+ @buffer_of_lines[@previous_line_index] = @line
515
+ @line = @buffer_of_lines[@line_index]
516
+ end
517
+ @first_line_started_from =
518
+ if @line_index.zero?
519
+ 0
520
+ else
521
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
522
+ end
523
+ if @prompt_proc
524
+ prompt = prompt_list[@line_index]
525
+ prompt_width = calculate_width(prompt, true)
526
+ end
527
+ move_cursor_down(@first_line_started_from)
528
+ calculate_nearest_cursor
529
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
530
+ move_cursor_down(@started_from)
531
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
532
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
533
+ end
534
+
535
+ private def rerender_all_lines
536
+ move_cursor_up(@first_line_started_from + @started_from)
537
+ Reline::IOGate.move_cursor_column(0)
538
+ back = 0
539
+ new_buffer = whole_lines
540
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
541
+ new_buffer.each_with_index do |line, index|
542
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
543
+ width = prompt_width + calculate_width(line)
544
+ height = calculate_height_by_width(width)
545
+ back += height
546
+ end
547
+ old_highest_in_all = @highest_in_all
548
+ if @line_index.zero?
549
+ new_first_line_started_from = 0
550
+ else
551
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
552
+ end
553
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
554
+ if back > old_highest_in_all
555
+ scroll_down(back - 1)
556
+ move_cursor_up(back - 1)
557
+ elsif back < old_highest_in_all
558
+ scroll_down(back)
559
+ Reline::IOGate.erase_after_cursor
560
+ (old_highest_in_all - back - 1).times do
561
+ scroll_down(1)
562
+ Reline::IOGate.erase_after_cursor
563
+ end
564
+ move_cursor_up(old_highest_in_all - 1)
565
+ end
566
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
567
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
568
+ if @prompt_proc
569
+ prompt = prompt_list[@line_index]
570
+ prompt_width = calculate_width(prompt, true)
571
+ end
572
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
573
+ @highest_in_all = back
574
+ @first_line_started_from = new_first_line_started_from
575
+ @started_from = new_started_from
576
+ if @scroll_partial_screen
577
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
578
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
579
+ else
580
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
581
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
582
+ end
583
+ end
584
+
585
+ private def render_whole_lines(lines, prompt, prompt_width)
586
+ rendered_height = 0
587
+ modify_lines(lines).each_with_index do |line, index|
588
+ if prompt.is_a?(Array)
589
+ line_prompt = prompt[index]
590
+ prompt_width = calculate_width(line_prompt, true)
591
+ else
592
+ line_prompt = prompt
593
+ end
594
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
595
+ if index < (lines.size - 1)
596
+ if @scroll_partial_screen
597
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
598
+ move_cursor_down(1)
599
+ end
600
+ else
601
+ scroll_down(1)
602
+ end
603
+ rendered_height += height
604
+ else
605
+ rendered_height += height - 1
606
+ end
607
+ end
608
+ rendered_height
609
+ end
610
+
611
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
476
612
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
613
+ cursor_up_from_last_line = 0
614
+ # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
615
+ if @scroll_partial_screen
616
+ last_visual_line = this_started_from + (height - 1)
617
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
618
+ if (@scroll_partial_screen - this_started_from) >= height
619
+ # Render nothing because this line is before the screen.
620
+ visual_lines = []
621
+ elsif this_started_from > last_screen_line
622
+ # Render nothing because this line is after the screen.
623
+ visual_lines = []
624
+ else
625
+ deleted_lines_before_screen = []
626
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
627
+ # A part of visual lines are before the screen.
628
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
629
+ deleted_lines_before_screen.compact!
630
+ end
631
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
632
+ # A part of visual lines are after the screen.
633
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
634
+ end
635
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
636
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
637
+ end
638
+ end
477
639
  if with_control
478
640
  if height > @highest_in_this
479
641
  diff = height - @highest_in_this
@@ -487,10 +649,14 @@ class Reline::LineEditor
487
649
  @highest_in_this = height
488
650
  end
489
651
  move_cursor_up(@started_from)
652
+ cursor_up_from_last_line = height - 1 - @started_from
490
653
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
491
654
  end
492
- Reline::IOGate.move_cursor_column(0)
655
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
656
+ @output.write "\e[0m" # clear character decorations
657
+ end
493
658
  visual_lines.each_with_index do |line, index|
659
+ Reline::IOGate.move_cursor_column(0)
494
660
  if line.nil?
495
661
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
496
662
  # reaches the end of line
@@ -522,14 +688,18 @@ class Reline::LineEditor
522
688
  @pre_input_hook&.call
523
689
  end
524
690
  end
525
- Reline::IOGate.erase_after_cursor
691
+ unless visual_lines.empty?
692
+ Reline::IOGate.erase_after_cursor
693
+ Reline::IOGate.move_cursor_column(0)
694
+ end
526
695
  if with_control
527
696
  # Just after rendring, so the cursor is on the last line.
528
697
  if finished?
529
698
  Reline::IOGate.move_cursor_column(0)
530
699
  else
531
700
  # Moves up from bottom of lines to the cursor position.
532
- move_cursor_up(height - 1 - @started_from)
701
+ move_cursor_up(cursor_up_from_last_line)
702
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
533
703
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
534
704
  end
535
705
  end
@@ -537,7 +707,7 @@ class Reline::LineEditor
537
707
  end
538
708
 
539
709
  private def modify_lines(before)
540
- return before if before.nil? || before.empty?
710
+ return before if before.nil? || before.empty? || simplified_rendering?
541
711
 
542
712
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
543
713
  after.lines("\n").map { |l| l.chomp('') }
@@ -546,6 +716,39 @@ class Reline::LineEditor
546
716
  end
547
717
  end
548
718
 
719
+ private def show_menu
720
+ scroll_down(@highest_in_all - @first_line_started_from)
721
+ @rerender_all = true
722
+ @menu_info.list.sort!.each do |item|
723
+ Reline::IOGate.move_cursor_column(0)
724
+ @output.write item
725
+ @output.flush
726
+ scroll_down(1)
727
+ end
728
+ scroll_down(@highest_in_all - 1)
729
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
730
+ end
731
+
732
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
733
+ Reline::IOGate.clear_screen
734
+ back = 0
735
+ modify_lines(whole_lines).each_with_index do |line, index|
736
+ if @prompt_proc
737
+ pr = prompt_list[index]
738
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
739
+ else
740
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
741
+ end
742
+ if index < (@buffer_of_lines.size - 1)
743
+ move_cursor_down(height)
744
+ back += height
745
+ end
746
+ end
747
+ move_cursor_up(back)
748
+ move_cursor_down(@first_line_started_from + @started_from)
749
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
750
+ end
751
+
549
752
  def editing_mode
550
753
  @config.editing_mode
551
754
  end
@@ -565,7 +768,7 @@ class Reline::LineEditor
565
768
  else
566
769
  i&.start_with?(target)
567
770
  end
568
- }
771
+ }.uniq
569
772
  if is_menu
570
773
  menu(target, list)
571
774
  return nil
@@ -682,7 +885,8 @@ class Reline::LineEditor
682
885
  if @waiting_operator_proc
683
886
  if VI_MOTIONS.include?(method_symbol)
684
887
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
685
- block.()
888
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
889
+ block.(true)
686
890
  unless @waiting_proc
687
891
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
688
892
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
@@ -690,27 +894,56 @@ class Reline::LineEditor
690
894
  else
691
895
  old_waiting_proc = @waiting_proc
692
896
  old_waiting_operator_proc = @waiting_operator_proc
897
+ current_waiting_operator_proc = @waiting_operator_proc
693
898
  @waiting_proc = proc { |k|
694
899
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
695
900
  old_waiting_proc.(k)
696
901
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
697
902
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
698
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
903
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
699
904
  @waiting_operator_proc = old_waiting_operator_proc
700
905
  }
701
906
  end
702
907
  else
703
908
  # Ignores operator when not motion is given.
704
- block.()
909
+ block.(false)
705
910
  end
706
911
  @waiting_operator_proc = nil
912
+ @waiting_operator_vi_arg = nil
913
+ @vi_arg = nil
707
914
  else
708
- block.()
915
+ block.(false)
709
916
  end
710
917
  end
711
918
 
712
919
  private def argumentable?(method_obj)
713
- method_obj and method_obj.parameters.length != 1
920
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
921
+ end
922
+
923
+ private def inclusive?(method_obj)
924
+ # If a motion method with the keyword argument "inclusive" follows the
925
+ # operator, it must contain the character at the cursor position.
926
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
927
+ end
928
+
929
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
930
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
931
+ not_insertion = method_symbol != :ed_insert
932
+ process_insert(force: not_insertion)
933
+ end
934
+ if @vi_arg and argumentable?(method_obj)
935
+ if with_operator and inclusive?(method_obj)
936
+ method_obj.(key, arg: @vi_arg, inclusive: true)
937
+ else
938
+ method_obj.(key, arg: @vi_arg)
939
+ end
940
+ else
941
+ if with_operator and inclusive?(method_obj)
942
+ method_obj.(key, inclusive: true)
943
+ else
944
+ method_obj.(key)
945
+ end
946
+ end
714
947
  end
715
948
 
716
949
  private def process_key(key, method_symbol)
@@ -721,11 +954,11 @@ class Reline::LineEditor
721
954
  end
722
955
  if method_symbol and key.is_a?(Symbol)
723
956
  if @vi_arg and argumentable?(method_obj)
724
- run_for_operators(key, method_symbol) do
725
- method_obj.(key, arg: @vi_arg)
957
+ run_for_operators(key, method_symbol) do |with_operator|
958
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
726
959
  end
727
960
  else
728
- method_obj&.(key)
961
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
729
962
  end
730
963
  @kill_ring.process
731
964
  @vi_arg = nil
@@ -734,15 +967,15 @@ class Reline::LineEditor
734
967
  ed_argument_digit(key)
735
968
  else
736
969
  if argumentable?(method_obj)
737
- run_for_operators(key, method_symbol) do
738
- method_obj.(key, arg: @vi_arg)
970
+ run_for_operators(key, method_symbol) do |with_operator|
971
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
739
972
  end
740
973
  elsif @waiting_proc
741
974
  @waiting_proc.(key)
742
975
  elsif method_obj
743
- method_obj.(key)
976
+ wrap_method_call(method_symbol, method_obj, key)
744
977
  else
745
- ed_insert(key)
978
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
746
979
  end
747
980
  @kill_ring.process
748
981
  @vi_arg = nil
@@ -752,15 +985,15 @@ class Reline::LineEditor
752
985
  @kill_ring.process
753
986
  elsif method_obj
754
987
  if method_symbol == :ed_argument_digit
755
- method_obj.(key)
988
+ wrap_method_call(method_symbol, method_obj, key)
756
989
  else
757
- run_for_operators(key, method_symbol) do
758
- method_obj.(key)
990
+ run_for_operators(key, method_symbol) do |with_operator|
991
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
759
992
  end
760
993
  end
761
994
  @kill_ring.process
762
995
  else
763
- ed_insert(key)
996
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
764
997
  end
765
998
  end
766
999
 
@@ -803,6 +1036,7 @@ class Reline::LineEditor
803
1036
  end
804
1037
 
805
1038
  def input_key(key)
1039
+ @just_cursor_moving = nil
806
1040
  if key.char.nil?
807
1041
  if @first_char
808
1042
  @line = nil
@@ -810,6 +1044,7 @@ class Reline::LineEditor
810
1044
  finish
811
1045
  return
812
1046
  end
1047
+ old_line = @line.dup
813
1048
  @first_char = false
814
1049
  completion_occurs = false
815
1050
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -817,6 +1052,7 @@ class Reline::LineEditor
817
1052
  result = call_completion_proc
818
1053
  if result.is_a?(Array)
819
1054
  completion_occurs = true
1055
+ process_insert
820
1056
  complete(result)
821
1057
  end
822
1058
  end
@@ -825,6 +1061,7 @@ class Reline::LineEditor
825
1061
  result = call_completion_proc
826
1062
  if result.is_a?(Array)
827
1063
  completion_occurs = true
1064
+ process_insert
828
1065
  move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
829
1066
  end
830
1067
  end
@@ -836,7 +1073,18 @@ class Reline::LineEditor
836
1073
  unless completion_occurs
837
1074
  @completion_state = CompletionState::NORMAL
838
1075
  end
839
- if @is_multiline and @auto_indent_proc
1076
+ if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1077
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1078
+ @just_cursor_moving = true
1079
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1080
+ @just_cursor_moving = true
1081
+ else
1082
+ @just_cursor_moving = false
1083
+ end
1084
+ else
1085
+ @just_cursor_moving = false
1086
+ end
1087
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
840
1088
  process_auto_indent
841
1089
  end
842
1090
  end
@@ -1038,6 +1286,7 @@ class Reline::LineEditor
1038
1286
 
1039
1287
  def finish
1040
1288
  @finished = true
1289
+ @rerender_all = true
1041
1290
  @config.reset
1042
1291
  end
1043
1292
 
@@ -1066,6 +1315,9 @@ class Reline::LineEditor
1066
1315
 
1067
1316
  private def key_newline(key)
1068
1317
  if @is_multiline
1318
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1319
+ @add_newline_to_end_of_buffer = true
1320
+ end
1069
1321
  next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1070
1322
  cursor_line = @line.byteslice(0, @byte_pointer)
1071
1323
  insert_new_line(cursor_line, next_line)
@@ -1076,38 +1328,62 @@ class Reline::LineEditor
1076
1328
 
1077
1329
  private def ed_unassigned(key) end # do nothing
1078
1330
 
1331
+ private def process_insert(force: false)
1332
+ return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1333
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1334
+ bytesize = @continuous_insertion_buffer.bytesize
1335
+ if @cursor == @cursor_max
1336
+ @line += @continuous_insertion_buffer
1337
+ else
1338
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1339
+ end
1340
+ @byte_pointer += bytesize
1341
+ @cursor += width
1342
+ @cursor_max += width
1343
+ @continuous_insertion_buffer.clear
1344
+ end
1345
+
1079
1346
  private def ed_insert(key)
1347
+ str = nil
1348
+ width = nil
1349
+ bytesize = nil
1080
1350
  if key.instance_of?(String)
1081
1351
  begin
1082
1352
  key.encode(Encoding::UTF_8)
1083
1353
  rescue Encoding::UndefinedConversionError
1084
1354
  return
1085
1355
  end
1086
- width = Reline::Unicode.get_mbchar_width(key)
1087
- if @cursor == @cursor_max
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
1356
+ str = key
1357
+ bytesize = key.bytesize
1095
1358
  else
1096
1359
  begin
1097
1360
  key.chr.encode(Encoding::UTF_8)
1098
1361
  rescue Encoding::UndefinedConversionError
1099
1362
  return
1100
1363
  end
1101
- if @cursor == @cursor_max
1102
- @line += key.chr
1103
- else
1104
- @line = byteinsert(@line, @byte_pointer, key.chr)
1105
- end
1106
- width = Reline::Unicode.get_mbchar_width(key.chr)
1107
- @byte_pointer += 1
1108
- @cursor += width
1109
- @cursor_max += width
1364
+ str = key.chr
1365
+ bytesize = 1
1366
+ end
1367
+ if Reline::IOGate.in_pasting?
1368
+ @continuous_insertion_buffer << str
1369
+ return
1370
+ elsif not @continuous_insertion_buffer.empty?
1371
+ process_insert
1372
+ end
1373
+ width = Reline::Unicode.get_mbchar_width(str)
1374
+ if @cursor == @cursor_max
1375
+ @line += str
1376
+ else
1377
+ @line = byteinsert(@line, @byte_pointer, str)
1378
+ end
1379
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1380
+ @byte_pointer += bytesize
1381
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1382
+ if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1383
+ width = 0
1110
1384
  end
1385
+ @cursor += width
1386
+ @cursor_max += width
1111
1387
  end
1112
1388
  alias_method :ed_digit, :ed_insert
1113
1389
  alias_method :self_insert, :ed_insert
@@ -1164,6 +1440,7 @@ class Reline::LineEditor
1164
1440
  arg -= 1
1165
1441
  ed_prev_char(key, arg: arg) if arg > 0
1166
1442
  end
1443
+ alias_method :backward_char, :ed_prev_char
1167
1444
 
1168
1445
  private def vi_first_print(key)
1169
1446
  @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
@@ -1593,6 +1870,7 @@ class Reline::LineEditor
1593
1870
  end
1594
1871
 
1595
1872
  private def ed_newline(key)
1873
+ process_insert(force: true)
1596
1874
  if @is_multiline
1597
1875
  if @config.editing_mode_is?(:vi_command)
1598
1876
  if @line_index < (@buffer_of_lines.size - 1)
@@ -1671,6 +1949,7 @@ class Reline::LineEditor
1671
1949
  @cursor = 0
1672
1950
  end
1673
1951
  end
1952
+ alias_method :kill_line, :em_kill_line
1674
1953
 
1675
1954
  private def em_delete(key)
1676
1955
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1721,6 +2000,7 @@ class Reline::LineEditor
1721
2000
  @byte_pointer += yanked.bytesize
1722
2001
  end
1723
2002
  end
2003
+ alias_method :yank, :em_yank
1724
2004
 
1725
2005
  private def em_yank_pop(key)
1726
2006
  yanked, prev_yank = @kill_ring.yank_pop
@@ -1737,6 +2017,7 @@ class Reline::LineEditor
1737
2017
  @byte_pointer += yanked.bytesize
1738
2018
  end
1739
2019
  end
2020
+ alias_method :yank_pop, :em_yank_pop
1740
2021
 
1741
2022
  private def ed_clear_screen(key)
1742
2023
  @cleared = true
@@ -1867,9 +2148,10 @@ class Reline::LineEditor
1867
2148
  @byte_pointer -= byte_size
1868
2149
  @cursor -= width
1869
2150
  @cursor_max -= width
1870
- @kill_ring.append(deleted)
2151
+ @kill_ring.append(deleted, true)
1871
2152
  end
1872
2153
  end
2154
+ alias_method :unix_word_rubout, :em_kill_region
1873
2155
 
1874
2156
  private def copy_for_vi(text)
1875
2157
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@@ -1890,7 +2172,7 @@ class Reline::LineEditor
1890
2172
  ed_prev_char(key)
1891
2173
  @config.editing_mode = :vi_command
1892
2174
  end
1893
- alias_method :backward_char, :ed_prev_char
2175
+ alias_method :vi_movement_mode, :vi_command_mode
1894
2176
 
1895
2177
  private def vi_next_word(key, arg: 1)
1896
2178
  if @line.bytesize > @byte_pointer
@@ -1912,13 +2194,22 @@ class Reline::LineEditor
1912
2194
  vi_prev_word(key, arg: arg) if arg > 0
1913
2195
  end
1914
2196
 
1915
- private def vi_end_word(key, arg: 1)
2197
+ private def vi_end_word(key, arg: 1, inclusive: false)
1916
2198
  if @line.bytesize > @byte_pointer
1917
2199
  byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1918
2200
  @byte_pointer += byte_size
1919
2201
  @cursor += width
1920
2202
  end
1921
2203
  arg -= 1
2204
+ if inclusive and arg.zero?
2205
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2206
+ if byte_size > 0
2207
+ c = @line.byteslice(@byte_pointer, byte_size)
2208
+ width = Reline::Unicode.get_mbchar_width(c)
2209
+ @byte_pointer += byte_size
2210
+ @cursor += width
2211
+ end
2212
+ end
1922
2213
  vi_end_word(key, arg: arg) if arg > 0
1923
2214
  end
1924
2215
 
@@ -1942,13 +2233,22 @@ class Reline::LineEditor
1942
2233
  vi_prev_big_word(key, arg: arg) if arg > 0
1943
2234
  end
1944
2235
 
1945
- private def vi_end_big_word(key, arg: 1)
2236
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1946
2237
  if @line.bytesize > @byte_pointer
1947
2238
  byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1948
2239
  @byte_pointer += byte_size
1949
2240
  @cursor += width
1950
2241
  end
1951
2242
  arg -= 1
2243
+ if inclusive and arg.zero?
2244
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2245
+ if byte_size > 0
2246
+ c = @line.byteslice(@byte_pointer, byte_size)
2247
+ width = Reline::Unicode.get_mbchar_width(c)
2248
+ @byte_pointer += byte_size
2249
+ @cursor += width
2250
+ end
2251
+ end
1952
2252
  vi_end_big_word(key, arg: arg) if arg > 0
1953
2253
  end
1954
2254
 
@@ -2003,7 +2303,7 @@ class Reline::LineEditor
2003
2303
  @cursor = 0
2004
2304
  end
2005
2305
 
2006
- private def vi_change_meta(key)
2306
+ private def vi_change_meta(key, arg: 1)
2007
2307
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2008
2308
  if byte_pointer_diff > 0
2009
2309
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2016,9 +2316,10 @@ class Reline::LineEditor
2016
2316
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2017
2317
  @config.editing_mode = :vi_insert
2018
2318
  }
2319
+ @waiting_operator_vi_arg = arg
2019
2320
  end
2020
2321
 
2021
- private def vi_delete_meta(key)
2322
+ private def vi_delete_meta(key, arg: 1)
2022
2323
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2023
2324
  if byte_pointer_diff > 0
2024
2325
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2030,9 +2331,19 @@ class Reline::LineEditor
2030
2331
  @cursor_max -= cursor_diff.abs
2031
2332
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2032
2333
  }
2334
+ @waiting_operator_vi_arg = arg
2033
2335
  end
2034
2336
 
2035
- private def vi_yank(key)
2337
+ private def vi_yank(key, arg: 1)
2338
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2339
+ if byte_pointer_diff > 0
2340
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2341
+ elsif byte_pointer_diff < 0
2342
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2343
+ end
2344
+ copy_for_vi(cut)
2345
+ }
2346
+ @waiting_operator_vi_arg = arg
2036
2347
  end
2037
2348
 
2038
2349
  private def vi_list_or_eof(key)
@@ -2177,15 +2488,15 @@ class Reline::LineEditor
2177
2488
  }
2178
2489
  end
2179
2490
 
2180
- private def vi_next_char(key, arg: 1)
2181
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
2491
+ private def vi_next_char(key, arg: 1, inclusive: false)
2492
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2182
2493
  end
2183
2494
 
2184
- private def vi_to_next_char(key, arg: 1)
2185
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
2495
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2496
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2186
2497
  end
2187
2498
 
2188
- private def search_next_char(key, arg, need_prev_char = false)
2499
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2189
2500
  if key.instance_of?(String)
2190
2501
  inputed_char = key
2191
2502
  else
@@ -2222,6 +2533,15 @@ class Reline::LineEditor
2222
2533
  @byte_pointer += byte_size
2223
2534
  @cursor += width
2224
2535
  end
2536
+ if inclusive
2537
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2538
+ if byte_size > 0
2539
+ c = @line.byteslice(@byte_pointer, byte_size)
2540
+ width = Reline::Unicode.get_mbchar_width(c)
2541
+ @byte_pointer += byte_size
2542
+ @cursor += width
2543
+ end
2544
+ end
2225
2545
  @waiting_proc = nil
2226
2546
  end
2227
2547
 
@@ -2293,6 +2613,7 @@ class Reline::LineEditor
2293
2613
  alias_method :set_mark, :em_set_mark
2294
2614
 
2295
2615
  private def em_exchange_mark(key)
2616
+ return unless @mark_pointer
2296
2617
  new_pointer = [@byte_pointer, @line_index]
2297
2618
  @previous_line_index = @line_index
2298
2619
  @byte_pointer, @line_index = @mark_pointer