reline 0.1.9 → 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: 981888e54748ace72084309bf4d0b832503970956188b3865629da1c577b5edb
4
- data.tar.gz: 11815b8b07d66ef247ab83e37d6c8884fdc4745e681b298237e2e32b631c4cbb
3
+ metadata.gz: 99b4f2cf521a3b774a0c763517fc373fdc2bf1603e64ab8e853513ed64946074
4
+ data.tar.gz: 4665d8f6c3db58c573186b4fb245478a1190fdb5da5f9c5f0d569bcfb2cba2f4
5
5
  SHA512:
6
- metadata.gz: 8b9df7fa6a4e7196773314b8e5287021fbdfcdd81372740c0c32d60c03cc4aed8fda02f5b14e337c428c160b1ff5a2c4144f5a5866af8f57e50fcb25b51b9099
7
- data.tar.gz: 50920eaeb2ef94d831c4eb4d798e41ccf9c7a826f237b1fc3343969e6af2ea05332cb38e4638c2fa439a29231a3789b0f4d0ee731d3106aaa6f6d08b12cbcb39
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
@@ -243,6 +248,10 @@ module Reline
243
248
  line_editor.input_key(c)
244
249
  line_editor.rerender
245
250
  }
251
+ if @bracketed_paste_finished
252
+ line_editor.rerender_all
253
+ @bracketed_paste_finished = false
254
+ end
246
255
  }
247
256
  if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
248
257
  prev_pasting_state = false
@@ -275,8 +284,13 @@ module Reline
275
284
  buffer = []
276
285
  loop do
277
286
  c = Reline::IOGate.getc
278
- buffer << c
279
- 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
280
294
  case result
281
295
  when :matched
282
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,8 +81,48 @@ 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
+
83
124
  def self.in_pasting?
84
- not Reline::IOGate.empty_buffer?
125
+ @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
85
126
  end
86
127
 
87
128
  def self.empty_buffer?
@@ -131,7 +172,7 @@ class Reline::ANSI
131
172
 
132
173
  def self.cursor_pos
133
174
  begin
134
- res = ''
175
+ res = +''
135
176
  m = nil
136
177
  @@input.raw do |stdin|
137
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|
@@ -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-^[
@@ -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,6 +50,8 @@ 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 = ''
@@ -59,6 +61,8 @@ class Reline::LineEditor
59
61
  def simplified_rendering?
60
62
  if finished?
61
63
  false
64
+ elsif @just_cursor_moving and not @rerender_all
65
+ true
62
66
  else
63
67
  not @rerender_all and not finished? and Reline::IOGate.in_pasting?
64
68
  end
@@ -76,7 +80,20 @@ class Reline::LineEditor
76
80
  end
77
81
  return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
78
82
  if @prompt_proc
79
- 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
80
97
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
81
98
  if @config.show_mode_in_prompt
82
99
  if @config.editing_mode_is?(:vi_command)
@@ -114,6 +131,7 @@ class Reline::LineEditor
114
131
  def reset(prompt = '', encoding:)
115
132
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
116
133
  @screen_size = Reline::IOGate.get_screen_size
134
+ @screen_height = @screen_size.first
117
135
  reset_variables(prompt, encoding: encoding)
118
136
  @old_trap = Signal.trap('SIGINT') {
119
137
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
@@ -123,6 +141,7 @@ class Reline::LineEditor
123
141
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
124
142
  old_screen_size = @screen_size
125
143
  @screen_size = Reline::IOGate.get_screen_size
144
+ @screen_height = @screen_size.first
126
145
  if old_screen_size.last < @screen_size.last # columns increase
127
146
  @rerender_all = true
128
147
  rerender
@@ -174,7 +193,7 @@ class Reline::LineEditor
174
193
  @cleared = false
175
194
  @rerender_all = false
176
195
  @history_pointer = nil
177
- @kill_ring = Reline::KillRing.new
196
+ @kill_ring ||= Reline::KillRing.new
178
197
  @vi_clipboard = ''
179
198
  @vi_arg = nil
180
199
  @waiting_proc = nil
@@ -188,8 +207,12 @@ class Reline::LineEditor
188
207
  @searching_prompt = nil
189
208
  @first_char = true
190
209
  @add_newline_to_end_of_buffer = false
210
+ @just_cursor_moving = nil
211
+ @cached_prompt_list = nil
212
+ @prompt_cache_time = nil
191
213
  @eof = false
192
214
  @continuous_insertion_buffer = String.new(encoding: @encoding)
215
+ @scroll_partial_screen = nil
193
216
  reset_line
194
217
  end
195
218
 
@@ -234,6 +257,7 @@ class Reline::LineEditor
234
257
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
235
258
  @previous_line_index = @line_index
236
259
  @line_index += 1
260
+ @just_cursor_moving = false
237
261
  end
238
262
 
239
263
  private def calculate_height_by_width(width)
@@ -274,28 +298,28 @@ class Reline::LineEditor
274
298
  end
275
299
  end
276
300
 
277
- private def calculate_nearest_cursor
278
- @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)
279
303
  new_cursor = 0
280
304
  new_byte_pointer = 0
281
305
  height = 1
282
306
  max_width = @screen_size.last
283
307
  if @config.editing_mode_is?(:vi_command)
284
- 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)
285
309
  if last_byte_size > 0
286
- 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)
287
311
  last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
288
- cursor_max = @cursor_max - last_width
312
+ end_of_line_cursor = new_cursor_max - last_width
289
313
  else
290
- cursor_max = @cursor_max
314
+ end_of_line_cursor = new_cursor_max
291
315
  end
292
316
  else
293
- cursor_max = @cursor_max
317
+ end_of_line_cursor = new_cursor_max
294
318
  end
295
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
319
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
296
320
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
297
321
  now = new_cursor + mbchar_width
298
- if now > cursor_max or now > @cursor
322
+ if now > end_of_line_cursor or now > cursor
299
323
  break
300
324
  end
301
325
  new_cursor += mbchar_width
@@ -304,13 +328,20 @@ class Reline::LineEditor
304
328
  end
305
329
  new_byte_pointer += gc.bytesize
306
330
  end
307
- @started_from = height - 1
308
- @cursor = new_cursor
309
- @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
310
340
  end
311
341
 
312
342
  def rerender_all
313
343
  @rerender_all = true
344
+ process_insert(force: true)
314
345
  rerender
315
346
  end
316
347
 
@@ -319,183 +350,53 @@ class Reline::LineEditor
319
350
  if @menu_info
320
351
  scroll_down(@highest_in_all - @first_line_started_from)
321
352
  @rerender_all = true
322
- @menu_info.list.sort!.each do |item|
323
- Reline::IOGate.move_cursor_column(0)
324
- @output.write item
325
- @output.flush
326
- scroll_down(1)
327
- end
328
- scroll_down(@highest_in_all - 1)
329
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
353
+ end
354
+ if @menu_info
355
+ show_menu
330
356
  @menu_info = nil
331
357
  end
332
358
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
333
359
  if @cleared
334
- Reline::IOGate.clear_screen
360
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
335
361
  @cleared = false
336
- back = 0
337
- modify_lines(whole_lines).each_with_index do |line, index|
338
- if @prompt_proc
339
- pr = prompt_list[index]
340
- height = render_partial(pr, calculate_width(pr), line, false)
341
- else
342
- height = render_partial(prompt, prompt_width, line, false)
343
- end
344
- if index < (@buffer_of_lines.size - 1)
345
- move_cursor_down(height)
346
- back += height
347
- end
348
- end
349
- move_cursor_up(back)
350
- move_cursor_down(@first_line_started_from + @started_from)
351
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
352
362
  return
353
363
  end
354
364
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
355
365
  # FIXME: end of logical line sometimes breaks
356
366
  if @add_newline_to_end_of_buffer
357
- scroll_down(1)
358
- new_lines = whole_lines(index: @previous_line_index, line: @line)
359
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
360
- @buffer_of_lines[@previous_line_index] = @line
361
- @line = @buffer_of_lines[@line_index]
362
- render_partial(prompt, prompt_width, @line, false)
363
- @cursor = @cursor_max = calculate_width(@line)
364
- @byte_pointer = @line.bytesize
365
- @highest_in_all += @highest_in_this
366
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
367
- @first_line_started_from += @started_from + 1
368
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
369
- @previous_line_index = nil
367
+ rerender_added_newline
370
368
  @add_newline_to_end_of_buffer = false
371
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
372
- if @previous_line_index
373
- new_lines = whole_lines(index: @previous_line_index, line: @line)
374
- else
375
- new_lines = whole_lines
376
- end
377
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
378
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
379
- diff = all_height - @highest_in_all
380
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
381
- if diff > 0
382
- scroll_down(diff)
383
- move_cursor_up(all_height - 1)
384
- elsif diff < 0
385
- (-diff).times do
386
- Reline::IOGate.move_cursor_column(0)
387
- Reline::IOGate.erase_after_cursor
388
- move_cursor_up(1)
389
- end
390
- move_cursor_up(all_height - 1)
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
391
382
  else
392
- move_cursor_up(all_height - 1)
393
- end
394
- @highest_in_all = all_height
395
- back = 0
396
- modify_lines(new_lines).each_with_index do |line, index|
397
- if @prompt_proc
398
- prompt = prompt_list[index]
399
- prompt_width = calculate_width(prompt, true)
400
- end
401
- height = render_partial(prompt, prompt_width, line, false)
402
- if index < (new_lines.size - 1)
403
- scroll_down(1)
404
- back += height
405
- else
406
- back += height - 1
407
- end
408
- end
409
- move_cursor_up(back)
410
- if @previous_line_index
411
- @buffer_of_lines[@previous_line_index] = @line
412
- @line = @buffer_of_lines[@line_index]
413
- end
414
- @first_line_started_from =
415
- if @line_index.zero?
416
- 0
417
- else
418
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
419
- end
420
- if @prompt_proc
421
- prompt = prompt_list[@line_index]
422
- prompt_width = calculate_width(prompt, true)
423
- end
424
- move_cursor_down(@first_line_started_from)
425
- calculate_nearest_cursor
426
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
427
- move_cursor_down(@started_from)
428
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
429
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
430
- @previous_line_index = nil
431
- rendered = true
432
- elsif @rerender_all
433
- move_cursor_up(@first_line_started_from + @started_from)
434
- Reline::IOGate.move_cursor_column(0)
435
- back = 0
436
- new_buffer = whole_lines
437
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
438
- new_buffer.each_with_index do |line, index|
439
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
440
- width = prompt_width + calculate_width(line)
441
- height = calculate_height_by_width(width)
442
- back += height
443
- end
444
- if back > @highest_in_all
445
- scroll_down(back - 1)
446
- move_cursor_up(back - 1)
447
- elsif back < @highest_in_all
448
- scroll_down(back)
449
- Reline::IOGate.erase_after_cursor
450
- (@highest_in_all - back - 1).times do
451
- scroll_down(1)
452
- Reline::IOGate.erase_after_cursor
453
- end
454
- move_cursor_up(@highest_in_all - 1)
455
- end
456
- modify_lines(new_buffer).each_with_index do |line, index|
457
- if @prompt_proc
458
- prompt = prompt_list[index]
459
- prompt_width = calculate_width(prompt, true)
460
- end
461
- render_partial(prompt, prompt_width, line, false)
462
- if index < (new_buffer.size - 1)
463
- move_cursor_down(1)
464
- end
465
383
  end
466
- move_cursor_up(back - 1)
467
- if @prompt_proc
468
- prompt = prompt_list[@line_index]
469
- prompt_width = calculate_width(prompt, true)
470
- end
471
- @highest_in_all = back
472
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
473
- @first_line_started_from =
474
- if @line_index.zero?
475
- 0
476
- else
477
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
478
- end
479
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
480
- move_cursor_down(@first_line_started_from + @started_from)
481
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
482
- @rerender_all = false
483
- rendered = true
484
384
  end
485
385
  line = modify_lines(whole_lines)[@line_index]
486
386
  if @is_multiline
487
387
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
488
388
  if finished?
489
389
  # Always rerender on finish because output_modifier_proc may return a different output.
490
- render_partial(prompt, prompt_width, line)
390
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
491
391
  scroll_down(1)
492
392
  Reline::IOGate.move_cursor_column(0)
493
393
  Reline::IOGate.erase_after_cursor
494
394
  elsif not rendered
495
- render_partial(prompt, prompt_width, line)
395
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
496
396
  end
397
+ @buffer_of_lines[@line_index] = @line
497
398
  else
498
- render_partial(prompt, prompt_width, line)
399
+ render_partial(prompt, prompt_width, line, 0)
499
400
  if finished?
500
401
  scroll_down(1)
501
402
  Reline::IOGate.move_cursor_column(0)
@@ -504,8 +405,237 @@ class Reline::LineEditor
504
405
  end
505
406
  end
506
407
 
507
- 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)
508
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
509
639
  if with_control
510
640
  if height > @highest_in_this
511
641
  diff = height - @highest_in_this
@@ -519,10 +649,14 @@ class Reline::LineEditor
519
649
  @highest_in_this = height
520
650
  end
521
651
  move_cursor_up(@started_from)
652
+ cursor_up_from_last_line = height - 1 - @started_from
522
653
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
523
654
  end
524
- 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
525
658
  visual_lines.each_with_index do |line, index|
659
+ Reline::IOGate.move_cursor_column(0)
526
660
  if line.nil?
527
661
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
528
662
  # reaches the end of line
@@ -554,15 +688,18 @@ class Reline::LineEditor
554
688
  @pre_input_hook&.call
555
689
  end
556
690
  end
557
- Reline::IOGate.erase_after_cursor
558
- Reline::IOGate.move_cursor_column(0)
691
+ unless visual_lines.empty?
692
+ Reline::IOGate.erase_after_cursor
693
+ Reline::IOGate.move_cursor_column(0)
694
+ end
559
695
  if with_control
560
696
  # Just after rendring, so the cursor is on the last line.
561
697
  if finished?
562
698
  Reline::IOGate.move_cursor_column(0)
563
699
  else
564
700
  # Moves up from bottom of lines to the cursor position.
565
- 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.
566
703
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
567
704
  end
568
705
  end
@@ -579,6 +716,39 @@ class Reline::LineEditor
579
716
  end
580
717
  end
581
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
+
582
752
  def editing_mode
583
753
  @config.editing_mode
584
754
  end
@@ -866,6 +1036,7 @@ class Reline::LineEditor
866
1036
  end
867
1037
 
868
1038
  def input_key(key)
1039
+ @just_cursor_moving = nil
869
1040
  if key.char.nil?
870
1041
  if @first_char
871
1042
  @line = nil
@@ -873,6 +1044,7 @@ class Reline::LineEditor
873
1044
  finish
874
1045
  return
875
1046
  end
1047
+ old_line = @line.dup
876
1048
  @first_char = false
877
1049
  completion_occurs = false
878
1050
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -901,6 +1073,17 @@ class Reline::LineEditor
901
1073
  unless completion_occurs
902
1074
  @completion_state = CompletionState::NORMAL
903
1075
  end
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
904
1087
  if @is_multiline and @auto_indent_proc and not simplified_rendering?
905
1088
  process_auto_indent
906
1089
  end
@@ -1193,7 +1376,12 @@ class Reline::LineEditor
1193
1376
  else
1194
1377
  @line = byteinsert(@line, @byte_pointer, str)
1195
1378
  end
1379
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1196
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
1384
+ end
1197
1385
  @cursor += width
1198
1386
  @cursor_max += width
1199
1387
  end
@@ -1761,6 +1949,7 @@ class Reline::LineEditor
1761
1949
  @cursor = 0
1762
1950
  end
1763
1951
  end
1952
+ alias_method :kill_line, :em_kill_line
1764
1953
 
1765
1954
  private def em_delete(key)
1766
1955
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1811,6 +2000,7 @@ class Reline::LineEditor
1811
2000
  @byte_pointer += yanked.bytesize
1812
2001
  end
1813
2002
  end
2003
+ alias_method :yank, :em_yank
1814
2004
 
1815
2005
  private def em_yank_pop(key)
1816
2006
  yanked, prev_yank = @kill_ring.yank_pop
@@ -1827,6 +2017,7 @@ class Reline::LineEditor
1827
2017
  @byte_pointer += yanked.bytesize
1828
2018
  end
1829
2019
  end
2020
+ alias_method :yank_pop, :em_yank_pop
1830
2021
 
1831
2022
  private def ed_clear_screen(key)
1832
2023
  @cleared = true
@@ -1957,9 +2148,10 @@ class Reline::LineEditor
1957
2148
  @byte_pointer -= byte_size
1958
2149
  @cursor -= width
1959
2150
  @cursor_max -= width
1960
- @kill_ring.append(deleted)
2151
+ @kill_ring.append(deleted, true)
1961
2152
  end
1962
2153
  end
2154
+ alias_method :unix_word_rubout, :em_kill_region
1963
2155
 
1964
2156
  private def copy_for_vi(text)
1965
2157
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)