reline 0.1.9 → 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: 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)