reline 0.1.5 → 0.1.10

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 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