reline 0.1.10 → 0.2.4

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: 99b4f2cf521a3b774a0c763517fc373fdc2bf1603e64ab8e853513ed64946074
4
- data.tar.gz: 4665d8f6c3db58c573186b4fb245478a1190fdb5da5f9c5f0d569bcfb2cba2f4
3
+ metadata.gz: f165aeb5368335d41b08932f34d0457d612fe4dbe334748964c2bafaaa518b54
4
+ data.tar.gz: 4e268dfea3a18f61b6f0bdbbf4d7dd292b3f2d62e178835c971f7b9d3da96613
5
5
  SHA512:
6
- metadata.gz: 7b66388f8b9008f5008902e662133312006263622cf7d12d458f5611446b536baf7ff66197209e7b82ada8d34d1b0c8d6eb40c6be35315134c289ded8cc421f7
7
- data.tar.gz: 74ce2b03a99eb548c25050f606140a9fd62351e2cbe20037977f416cc20153f3d711e946c84c4857e66b078a33b7dd12a359f429a2c31091ece7e8f776204931
6
+ metadata.gz: 7c56912f662f9c0e53d793ff108ad44fa2ef231b07fa579ab11e793639b27dd076b6dcb54c7dcc1f68c52a73fbabddf8f41a9d8ea8ea53c06cee481628ea9e18
7
+ data.tar.gz: acc258dcbe8b5a3dbe536cc3084906bae2006972f7a3e34353f7f87bbb9b78d692e68b479abb412aae18c396efa2f8dc0210fabc04451cce3f48e7ac8a572093
data/README.md CHANGED
@@ -11,3 +11,7 @@ Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and
11
11
  ## License
12
12
 
13
13
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
14
+
15
+ ## Acknowledgments for [rb-readline](https://github.com/ConnorAtherton/rb-readline)
16
+
17
+ In developing Reline, we have used some of the rb-readline implementation, so this library includes [copyright notice, list of conditions and the disclaimer](license_of_rb-readline) under the 3-Clause BSD License. Reline would never have been developed without rb-readline. Thank you for the tremendous accomplishments.
data/lib/reline.rb CHANGED
@@ -36,7 +36,6 @@ module Reline
36
36
  attr_accessor :config
37
37
  attr_accessor :key_stroke
38
38
  attr_accessor :line_editor
39
- attr_accessor :ambiguous_width
40
39
  attr_accessor :last_incremental_search
41
40
  attr_reader :output
42
41
 
@@ -244,6 +243,7 @@ module Reline
244
243
  loop do
245
244
  prev_pasting_state = Reline::IOGate.in_pasting?
246
245
  read_io(config.keyseq_timeout) { |inputs|
246
+ line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
247
247
  inputs.each { |c|
248
248
  line_editor.input_key(c)
249
249
  line_editor.rerender
@@ -254,6 +254,7 @@ module Reline
254
254
  end
255
255
  }
256
256
  if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
257
+ line_editor.set_pasting_state(false)
257
258
  prev_pasting_state = false
258
259
  line_editor.rerender_all
259
260
  end
@@ -356,9 +357,14 @@ module Reline
356
357
  end
357
358
  end
358
359
 
360
+ def ambiguous_width
361
+ may_req_ambiguous_char_width unless defined? @ambiguous_width
362
+ @ambiguous_width
363
+ end
364
+
359
365
  private def may_req_ambiguous_char_width
360
366
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
361
- return if ambiguous_width
367
+ return if @ambiguous_width
362
368
  Reline::IOGate.move_cursor_column(0)
363
369
  begin
364
370
  output.write "\u{25bd}"
data/lib/reline/config.rb CHANGED
@@ -34,10 +34,11 @@ class Reline::Config
34
34
  show-all-if-unmodified
35
35
  visible-stats
36
36
  show-mode-in-prompt
37
- vi-cmd-mode-icon
38
- vi-ins-mode-icon
37
+ vi-cmd-mode-string
38
+ vi-ins-mode-string
39
39
  emacs-mode-string
40
40
  enable-bracketed-paste
41
+ isearch-terminators
41
42
  }
42
43
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
43
44
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -55,8 +56,8 @@ class Reline::Config
55
56
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
56
57
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
57
58
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
58
- @vi_cmd_mode_icon = '(cmd)'
59
- @vi_ins_mode_icon = '(ins)'
59
+ @vi_cmd_mode_string = '(cmd)'
60
+ @vi_ins_mode_string = '(ins)'
60
61
  @emacs_mode_string = '@'
61
62
  # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
62
63
  @history_size = -1 # unlimited
@@ -238,7 +239,7 @@ class Reline::Config
238
239
  when 'completion-query-items'
239
240
  @completion_query_items = value.to_i
240
241
  when 'isearch-terminators'
241
- @isearch_terminators = instance_eval(value)
242
+ @isearch_terminators = retrieve_string(value)
242
243
  when 'editing-mode'
243
244
  case value
244
245
  when 'emacs'
@@ -269,9 +270,9 @@ class Reline::Config
269
270
  @show_mode_in_prompt = false
270
271
  end
271
272
  when 'vi-cmd-mode-string'
272
- @vi_cmd_mode_icon = retrieve_string(value)
273
+ @vi_cmd_mode_string = retrieve_string(value)
273
274
  when 'vi-ins-mode-string'
274
- @vi_ins_mode_icon = retrieve_string(value)
275
+ @vi_ins_mode_string = retrieve_string(value)
275
276
  when 'emacs-mode-string'
276
277
  @emacs_mode_string = retrieve_string(value)
277
278
  when *VARIABLE_NAMES then
@@ -58,14 +58,38 @@ class Reline::LineEditor
58
58
  reset_variables(encoding: encoding)
59
59
  end
60
60
 
61
+ def set_pasting_state(in_pasting)
62
+ @in_pasting = in_pasting
63
+ end
64
+
61
65
  def simplified_rendering?
62
66
  if finished?
63
67
  false
64
68
  elsif @just_cursor_moving and not @rerender_all
65
69
  true
66
70
  else
67
- not @rerender_all and not finished? and Reline::IOGate.in_pasting?
71
+ not @rerender_all and not finished? and @in_pasting
72
+ end
73
+ end
74
+
75
+ private def check_mode_string
76
+ mode_string = nil
77
+ if @config.show_mode_in_prompt
78
+ if @config.editing_mode_is?(:vi_command)
79
+ mode_string = @config.vi_cmd_mode_string
80
+ elsif @config.editing_mode_is?(:vi_insert)
81
+ mode_string = @config.vi_ins_mode_string
82
+ elsif @config.editing_mode_is?(:emacs)
83
+ mode_string = @config.emacs_mode_string
84
+ else
85
+ mode_string = '?'
86
+ end
68
87
  end
88
+ if mode_string != @prev_mode_string
89
+ @rerender_all = true
90
+ end
91
+ @prev_mode_string = mode_string
92
+ mode_string
69
93
  end
70
94
 
71
95
  private def check_multiline_prompt(buffer, prompt)
@@ -78,7 +102,11 @@ class Reline::LineEditor
78
102
  else
79
103
  prompt = @prompt
80
104
  end
81
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
105
+ if simplified_rendering?
106
+ mode_string = check_mode_string
107
+ prompt = mode_string + prompt if mode_string
108
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
109
+ end
82
110
  if @prompt_proc
83
111
  use_cached_prompt_list = false
84
112
  if @cached_prompt_list
@@ -88,6 +116,7 @@ class Reline::LineEditor
88
116
  use_cached_prompt_list = true
89
117
  end
90
118
  end
119
+ use_cached_prompt_list = false if @rerender_all
91
120
  if use_cached_prompt_list
92
121
  prompt_list = @cached_prompt_list
93
122
  else
@@ -95,35 +124,23 @@ class Reline::LineEditor
95
124
  @prompt_cache_time = Time.now.to_f
96
125
  end
97
126
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
98
- if @config.show_mode_in_prompt
99
- if @config.editing_mode_is?(:vi_command)
100
- mode_icon = @config.vi_cmd_mode_icon
101
- elsif @config.editing_mode_is?(:vi_insert)
102
- mode_icon = @config.vi_ins_mode_icon
103
- elsif @config.editing_mode_is?(:emacs)
104
- mode_icon = @config.emacs_mode_string
105
- else
106
- mode_icon = '?'
127
+ prompt_list = [prompt] if prompt_list.empty?
128
+ mode_string = check_mode_string
129
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
130
+ prompt = prompt_list[@line_index]
131
+ prompt = prompt_list[0] if prompt.nil?
132
+ prompt = prompt_list.last if prompt.nil?
133
+ if buffer.size > prompt_list.size
134
+ (buffer.size - prompt_list.size).times do
135
+ prompt_list << prompt_list.last
107
136
  end
108
- prompt_list.map!{ |pr| mode_icon + pr }
109
137
  end
110
- prompt = prompt_list[@line_index]
111
138
  prompt_width = calculate_width(prompt, true)
112
139
  [prompt, prompt_width, prompt_list]
113
140
  else
141
+ mode_string = check_mode_string
142
+ prompt = mode_string + prompt if mode_string
114
143
  prompt_width = calculate_width(prompt, true)
115
- if @config.show_mode_in_prompt
116
- if @config.editing_mode_is?(:vi_command)
117
- mode_icon = @config.vi_cmd_mode_icon
118
- elsif @config.editing_mode_is?(:vi_insert)
119
- mode_icon = @config.vi_ins_mode_icon
120
- elsif @config.editing_mode_is?(:emacs)
121
- mode_icon = @config.emacs_mode_string
122
- else
123
- mode_icon = '?'
124
- end
125
- prompt = mode_icon + prompt
126
- end
127
144
  [prompt, prompt_width, nil]
128
145
  end
129
146
  end
@@ -134,6 +151,13 @@ class Reline::LineEditor
134
151
  @screen_height = @screen_size.first
135
152
  reset_variables(prompt, encoding: encoding)
136
153
  @old_trap = Signal.trap('SIGINT') {
154
+ if @scroll_partial_screen
155
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
156
+ else
157
+ move_cursor_down(@highest_in_all - @line_index - 1)
158
+ end
159
+ Reline::IOGate.move_cursor_column(0)
160
+ scroll_down(1)
137
161
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
138
162
  raise Interrupt
139
163
  }
@@ -213,6 +237,10 @@ class Reline::LineEditor
213
237
  @eof = false
214
238
  @continuous_insertion_buffer = String.new(encoding: @encoding)
215
239
  @scroll_partial_screen = nil
240
+ @prev_mode_string = nil
241
+ @drop_terminate_spaces = false
242
+ @in_pasting = false
243
+ @auto_indent_proc = nil
216
244
  reset_line
217
245
  end
218
246
 
@@ -316,8 +344,9 @@ class Reline::LineEditor
316
344
  else
317
345
  end_of_line_cursor = new_cursor_max
318
346
  end
319
- line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
320
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
347
+ line_to_calc.grapheme_clusters.each do |gc|
348
+ mbchar = gc.encode(Encoding::UTF_8)
349
+ mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
321
350
  now = new_cursor + mbchar_width
322
351
  if now > end_of_line_cursor or now > cursor
323
352
  break
@@ -361,10 +390,29 @@ class Reline::LineEditor
361
390
  @cleared = false
362
391
  return
363
392
  end
393
+ if @is_multiline and finished? and @scroll_partial_screen
394
+ # Re-output all code higher than the screen when finished.
395
+ Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
396
+ Reline::IOGate.move_cursor_column(0)
397
+ @scroll_partial_screen = nil
398
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
399
+ if @previous_line_index
400
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
401
+ else
402
+ new_lines = whole_lines
403
+ end
404
+ modify_lines(new_lines).each_with_index do |line, index|
405
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
406
+ Reline::IOGate.erase_after_cursor
407
+ end
408
+ @output.flush
409
+ return
410
+ end
364
411
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
365
412
  # FIXME: end of logical line sometimes breaks
413
+ rendered = false
366
414
  if @add_newline_to_end_of_buffer
367
- rerender_added_newline
415
+ rerender_added_newline(prompt, prompt_width)
368
416
  @add_newline_to_end_of_buffer = false
369
417
  else
370
418
  if @just_cursor_moving and not @rerender_all
@@ -382,20 +430,32 @@ class Reline::LineEditor
382
430
  else
383
431
  end
384
432
  end
385
- line = modify_lines(whole_lines)[@line_index]
386
433
  if @is_multiline
387
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
388
434
  if finished?
389
435
  # Always rerender on finish because output_modifier_proc may return a different output.
436
+ if @previous_line_index
437
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
438
+ else
439
+ new_lines = whole_lines
440
+ end
441
+ line = modify_lines(new_lines)[@line_index]
442
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
390
443
  render_partial(prompt, prompt_width, line, @first_line_started_from)
444
+ move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
391
445
  scroll_down(1)
392
446
  Reline::IOGate.move_cursor_column(0)
393
447
  Reline::IOGate.erase_after_cursor
394
448
  elsif not rendered
395
- render_partial(prompt, prompt_width, line, @first_line_started_from)
449
+ unless @in_pasting
450
+ line = modify_lines(whole_lines)[@line_index]
451
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
452
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
453
+ end
396
454
  end
397
455
  @buffer_of_lines[@line_index] = @line
456
+ @rest_height = 0 if @scroll_partial_screen
398
457
  else
458
+ line = modify_lines(whole_lines)[@line_index]
399
459
  render_partial(prompt, prompt_width, line, 0)
400
460
  if finished?
401
461
  scroll_down(1)
@@ -438,13 +498,13 @@ class Reline::LineEditor
438
498
  end
439
499
  end
440
500
 
441
- private def rerender_added_newline
501
+ private def rerender_added_newline(prompt, prompt_width)
442
502
  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
503
  @buffer_of_lines[@previous_line_index] = @line
446
504
  @line = @buffer_of_lines[@line_index]
447
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
505
+ unless @in_pasting
506
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
507
+ end
448
508
  @cursor = @cursor_max = calculate_width(@line)
449
509
  @byte_pointer = @line.bytesize
450
510
  @highest_in_all += @highest_in_this
@@ -464,7 +524,7 @@ class Reline::LineEditor
464
524
  calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
465
525
  end
466
526
  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)
527
+ new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
468
528
  new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
469
529
  calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
470
530
  @previous_line_index = nil
@@ -478,6 +538,8 @@ class Reline::LineEditor
478
538
  @first_line_started_from = new_first_line_started_from
479
539
  @started_from = new_started_from
480
540
  @cursor = new_cursor
541
+ @cursor_max = new_cursor_max
542
+ @byte_pointer = new_byte_pointer
481
543
  move_cursor_down(first_line_diff + @started_from)
482
544
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
483
545
  false
@@ -551,7 +613,13 @@ class Reline::LineEditor
551
613
  new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
552
614
  end
553
615
  new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
554
- if back > old_highest_in_all
616
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
617
+ if @scroll_partial_screen
618
+ move_cursor_up(@first_line_started_from + @started_from)
619
+ scroll_down(@screen_height - 1)
620
+ move_cursor_up(@screen_height)
621
+ Reline::IOGate.move_cursor_column(0)
622
+ elsif back > old_highest_in_all
555
623
  scroll_down(back - 1)
556
624
  move_cursor_up(back - 1)
557
625
  elsif back < old_highest_in_all
@@ -563,7 +631,6 @@ class Reline::LineEditor
563
631
  end
564
632
  move_cursor_up(old_highest_in_all - 1)
565
633
  end
566
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
567
634
  render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
568
635
  if @prompt_proc
569
636
  prompt = prompt_list[@line_index]
@@ -649,8 +716,8 @@ class Reline::LineEditor
649
716
  @highest_in_this = height
650
717
  end
651
718
  move_cursor_up(@started_from)
652
- cursor_up_from_last_line = height - 1 - @started_from
653
719
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
720
+ cursor_up_from_last_line = height - 1 - @started_from
654
721
  end
655
722
  if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
656
723
  @output.write "\e[0m" # clear character decorations
@@ -660,7 +727,7 @@ class Reline::LineEditor
660
727
  if line.nil?
661
728
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
662
729
  # reaches the end of line
663
- if Reline::IOGate.win?
730
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
664
731
  # A newline is automatically inserted if a character is rendered at
665
732
  # eol on command prompt.
666
733
  else
@@ -678,7 +745,7 @@ class Reline::LineEditor
678
745
  next
679
746
  end
680
747
  @output.write line
681
- if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
748
+ if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
682
749
  # A newline is automatically inserted if a character is rendered at eol on command prompt.
683
750
  @rest_height -= 1 if @rest_height > 0
684
751
  end
@@ -1073,7 +1140,7 @@ class Reline::LineEditor
1073
1140
  unless completion_occurs
1074
1141
  @completion_state = CompletionState::NORMAL
1075
1142
  end
1076
- if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1143
+ if not @in_pasting and @just_cursor_moving.nil?
1077
1144
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1078
1145
  @just_cursor_moving = true
1079
1146
  elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
@@ -1122,6 +1189,7 @@ class Reline::LineEditor
1122
1189
  new_lines = whole_lines
1123
1190
  end
1124
1191
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1192
+ new_indent = @cursor_max if new_indent&.> @cursor_max
1125
1193
  if new_indent&.>= 0
1126
1194
  md = new_lines[@line_index].match(/\A */)
1127
1195
  prev_indent = md[0].count(' ')
@@ -1276,7 +1344,11 @@ class Reline::LineEditor
1276
1344
  if @buffer_of_lines.size == 1 and @line.nil?
1277
1345
  nil
1278
1346
  else
1279
- whole_lines.join("\n")
1347
+ if @previous_line_index
1348
+ whole_lines(index: @previous_line_index, line: @line).join("\n")
1349
+ else
1350
+ whole_lines.join("\n")
1351
+ end
1280
1352
  end
1281
1353
  end
1282
1354
 
@@ -1322,14 +1394,14 @@ class Reline::LineEditor
1322
1394
  cursor_line = @line.byteslice(0, @byte_pointer)
1323
1395
  insert_new_line(cursor_line, next_line)
1324
1396
  @cursor = 0
1325
- @check_new_auto_indent = true
1397
+ @check_new_auto_indent = true unless @in_pasting
1326
1398
  end
1327
1399
  end
1328
1400
 
1329
1401
  private def ed_unassigned(key) end # do nothing
1330
1402
 
1331
1403
  private def process_insert(force: false)
1332
- return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1404
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1333
1405
  width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1334
1406
  bytesize = @continuous_insertion_buffer.bytesize
1335
1407
  if @cursor == @cursor_max
@@ -1364,7 +1436,7 @@ class Reline::LineEditor
1364
1436
  str = key.chr
1365
1437
  bytesize = 1
1366
1438
  end
1367
- if Reline::IOGate.in_pasting?
1439
+ if @in_pasting
1368
1440
  @continuous_insertion_buffer << str
1369
1441
  return
1370
1442
  elsif not @continuous_insertion_buffer.empty?
@@ -1593,9 +1665,11 @@ class Reline::LineEditor
1593
1665
  searcher = generate_searcher
1594
1666
  searcher.resume(key)
1595
1667
  @searching_prompt = "(reverse-i-search)`': "
1668
+ termination_keys = ["\C-j".ord]
1669
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1596
1670
  @waiting_proc = ->(k) {
1597
1671
  case k
1598
- when "\C-j".ord
1672
+ when *termination_keys
1599
1673
  if @history_pointer
1600
1674
  buffer = Reline::HISTORY[@history_pointer]
1601
1675
  else
@@ -1614,6 +1688,8 @@ class Reline::LineEditor
1614
1688
  @waiting_proc = nil
1615
1689
  @cursor_max = calculate_width(@line)
1616
1690
  @cursor = @byte_pointer = 0
1691
+ @rerender_all = true
1692
+ @cached_prompt_list = nil
1617
1693
  searcher.resume(-1)
1618
1694
  when "\C-g".ord
1619
1695
  if @is_multiline
@@ -1657,6 +1733,8 @@ class Reline::LineEditor
1657
1733
  @waiting_proc = nil
1658
1734
  @cursor_max = calculate_width(@line)
1659
1735
  @cursor = @byte_pointer = 0
1736
+ @rerender_all = true
1737
+ @cached_prompt_list = nil
1660
1738
  searcher.resume(-1)
1661
1739
  end
1662
1740
  end
@@ -1709,7 +1787,7 @@ class Reline::LineEditor
1709
1787
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1710
1788
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1711
1789
  @line_index = line_no
1712
- @line = @buffer_of_lines.last
1790
+ @line = @buffer_of_lines[@line_index]
1713
1791
  @rerender_all = true
1714
1792
  else
1715
1793
  @line = Reline::HISTORY[@history_pointer]
@@ -1757,7 +1835,7 @@ class Reline::LineEditor
1757
1835
  @line_index = line_no
1758
1836
  end
1759
1837
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1760
- @line = @buffer_of_lines.last
1838
+ @line = @buffer_of_lines[@line_index]
1761
1839
  @rerender_all = true
1762
1840
  else
1763
1841
  if @history_pointer.nil? and substr.empty?
@@ -2176,7 +2254,7 @@ class Reline::LineEditor
2176
2254
 
2177
2255
  private def vi_next_word(key, arg: 1)
2178
2256
  if @line.bytesize > @byte_pointer
2179
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2257
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2180
2258
  @byte_pointer += byte_size
2181
2259
  @cursor += width
2182
2260
  end
@@ -2304,6 +2382,7 @@ class Reline::LineEditor
2304
2382
  end
2305
2383
 
2306
2384
  private def vi_change_meta(key, arg: 1)
2385
+ @drop_terminate_spaces = true
2307
2386
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2308
2387
  if byte_pointer_diff > 0
2309
2388
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2315,6 +2394,7 @@ class Reline::LineEditor
2315
2394
  @cursor_max -= cursor_diff.abs
2316
2395
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2317
2396
  @config.editing_mode = :vi_insert
2397
+ @drop_terminate_spaces = false
2318
2398
  }
2319
2399
  @waiting_operator_vi_arg = arg
2320
2400
  end
@@ -2370,6 +2450,9 @@ class Reline::LineEditor
2370
2450
  width = Reline::Unicode.get_mbchar_width(mbchar)
2371
2451
  @cursor_max -= width
2372
2452
  if @cursor > 0 and @cursor >= @cursor_max
2453
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2454
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2455
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2373
2456
  @byte_pointer -= byte_size
2374
2457
  @cursor -= width
2375
2458
  end
@@ -2403,11 +2486,23 @@ class Reline::LineEditor
2403
2486
 
2404
2487
  private def vi_histedit(key)
2405
2488
  path = Tempfile.open { |fp|
2406
- fp.write @line
2489
+ if @is_multiline
2490
+ fp.write whole_lines.join("\n")
2491
+ else
2492
+ fp.write @line
2493
+ end
2407
2494
  fp.path
2408
2495
  }
2409
2496
  system("#{ENV['EDITOR']} #{path}")
2410
- @line = File.read(path)
2497
+ if @is_multiline
2498
+ @buffer_of_lines = File.read(path).split("\n")
2499
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2500
+ @line_index = 0
2501
+ @line = @buffer_of_lines[@line_index]
2502
+ @rerender_all = true
2503
+ else
2504
+ @line = File.read(path)
2505
+ end
2411
2506
  finish
2412
2507
  end
2413
2508
 
@@ -2466,7 +2561,7 @@ class Reline::LineEditor
2466
2561
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2467
2562
  before = @line.byteslice(0, @byte_pointer)
2468
2563
  remaining_point = @byte_pointer + byte_size
2469
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2564
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2470
2565
  @line = before + k.chr + after
2471
2566
  @cursor_max = calculate_width(@line)
2472
2567
  @waiting_proc = nil
@@ -2477,7 +2572,7 @@ class Reline::LineEditor
2477
2572
  end
2478
2573
  before = @line.byteslice(0, @byte_pointer)
2479
2574
  remaining_point = @byte_pointer + byte_size
2480
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2575
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2481
2576
  replaced = k.chr * arg
2482
2577
  @line = before + replaced + after
2483
2578
  @byte_pointer += replaced.bytesize
@@ -58,16 +58,40 @@ class Reline::LineEditor
58
58
  reset_variables(encoding: encoding)
59
59
  end
60
60
 
61
+ def set_pasting_state(in_pasting)
62
+ @in_pasting = in_pasting
63
+ end
64
+
61
65
  def simplified_rendering?
62
66
  if finished?
63
67
  false
64
68
  elsif @just_cursor_moving and not @rerender_all
65
69
  true
66
70
  else
67
- not @rerender_all and not finished? and Reline::IOGate.in_pasting?
71
+ not @rerender_all and not finished? and @in_pasting
68
72
  end
69
73
  end
70
74
 
75
+ private def check_mode_string
76
+ mode_string = nil
77
+ if @config.show_mode_in_prompt
78
+ if @config.editing_mode_is?(:vi_command)
79
+ mode_string = @config.vi_cmd_mode_string
80
+ elsif @config.editing_mode_is?(:vi_insert)
81
+ mode_string = @config.vi_ins_mode_string
82
+ elsif @config.editing_mode_is?(:emacs)
83
+ mode_string = @config.emacs_mode_string
84
+ else
85
+ mode_string = '?'
86
+ end
87
+ end
88
+ if mode_string != @prev_mode_string
89
+ @rerender_all = true
90
+ end
91
+ @prev_mode_string = mode_string
92
+ mode_string
93
+ end
94
+
71
95
  private def check_multiline_prompt(buffer, prompt)
72
96
  if @vi_arg
73
97
  prompt = "(arg: #{@vi_arg}) "
@@ -78,44 +102,44 @@ class Reline::LineEditor
78
102
  else
79
103
  prompt = @prompt
80
104
  end
81
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
105
+ if simplified_rendering?
106
+ mode_string = check_mode_string
107
+ prompt = mode_string + prompt if mode_string
108
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
109
+ end
82
110
  if @prompt_proc
83
- if @cached_prompt_list and Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
111
+ use_cached_prompt_list = false
112
+ if @cached_prompt_list
113
+ if @just_cursor_moving
114
+ use_cached_prompt_list = true
115
+ elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
116
+ use_cached_prompt_list = true
117
+ end
118
+ end
119
+ use_cached_prompt_list = false if @rerender_all
120
+ if use_cached_prompt_list
84
121
  prompt_list = @cached_prompt_list
85
122
  else
86
123
  prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
87
124
  @prompt_cache_time = Time.now.to_f
88
125
  end
89
126
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
90
- if @config.show_mode_in_prompt
91
- if @config.editing_mode_is?(:vi_command)
92
- mode_icon = @config.vi_cmd_mode_icon
93
- elsif @config.editing_mode_is?(:vi_insert)
94
- mode_icon = @config.vi_ins_mode_icon
95
- elsif @config.editing_mode_is?(:emacs)
96
- mode_icon = @config.emacs_mode_string
97
- else
98
- mode_icon = '?'
127
+ mode_string = check_mode_string
128
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
129
+ prompt = prompt_list[@line_index]
130
+ prompt = prompt_list[0] if prompt.nil?
131
+ prompt = prompt_list.last if prompt.nil?
132
+ if buffer.size > prompt_list.size
133
+ (buffer.size - prompt_list.size).times do
134
+ prompt_list << prompt_list.last
99
135
  end
100
- prompt_list.map!{ |pr| mode_icon + pr }
101
136
  end
102
- prompt = prompt_list[@line_index]
103
137
  prompt_width = calculate_width(prompt, true)
104
138
  [prompt, prompt_width, prompt_list]
105
139
  else
140
+ mode_string = check_mode_string
141
+ prompt = mode_string + prompt if mode_string
106
142
  prompt_width = calculate_width(prompt, true)
107
- if @config.show_mode_in_prompt
108
- if @config.editing_mode_is?(:vi_command)
109
- mode_icon = @config.vi_cmd_mode_icon
110
- elsif @config.editing_mode_is?(:vi_insert)
111
- mode_icon = @config.vi_ins_mode_icon
112
- elsif @config.editing_mode_is?(:emacs)
113
- mode_icon = @config.emacs_mode_string
114
- else
115
- mode_icon = '?'
116
- end
117
- prompt = mode_icon + prompt
118
- end
119
143
  [prompt, prompt_width, nil]
120
144
  end
121
145
  end
@@ -126,6 +150,13 @@ class Reline::LineEditor
126
150
  @screen_height = @screen_size.first
127
151
  reset_variables(prompt, encoding: encoding)
128
152
  @old_trap = Signal.trap('SIGINT') {
153
+ if @scroll_partial_screen
154
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
155
+ else
156
+ move_cursor_down(@highest_in_all - @line_index - 1)
157
+ end
158
+ Reline::IOGate.move_cursor_column(0)
159
+ scroll_down(1)
129
160
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
130
161
  raise Interrupt
131
162
  }
@@ -185,7 +216,7 @@ class Reline::LineEditor
185
216
  @cleared = false
186
217
  @rerender_all = false
187
218
  @history_pointer = nil
188
- @kill_ring = Reline::KillRing.new
219
+ @kill_ring ||= Reline::KillRing.new
189
220
  @vi_clipboard = ''
190
221
  @vi_arg = nil
191
222
  @waiting_proc = nil
@@ -205,6 +236,10 @@ class Reline::LineEditor
205
236
  @eof = false
206
237
  @continuous_insertion_buffer = String.new(encoding: @encoding)
207
238
  @scroll_partial_screen = nil
239
+ @prev_mode_string = nil
240
+ @drop_terminate_spaces = false
241
+ @in_pasting = false
242
+ @auto_indent_proc = nil
208
243
  reset_line
209
244
  end
210
245
 
@@ -353,10 +388,24 @@ class Reline::LineEditor
353
388
  @cleared = false
354
389
  return
355
390
  end
391
+ if @is_multiline and finished? and @scroll_partial_screen
392
+ # Re-output all code higher than the screen when finished.
393
+ Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
394
+ Reline::IOGate.move_cursor_column(0)
395
+ @scroll_partial_screen = nil
396
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
397
+ modify_lines(whole_lines).each_with_index do |line, index|
398
+ @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
399
+ Reline::IOGate.erase_after_cursor
400
+ end
401
+ @output.flush
402
+ return
403
+ end
356
404
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
357
405
  # FIXME: end of logical line sometimes breaks
406
+ rendered = false
358
407
  if @add_newline_to_end_of_buffer
359
- rerender_added_newline
408
+ rerender_added_newline(prompt, prompt_width)
360
409
  @add_newline_to_end_of_buffer = false
361
410
  else
362
411
  if @just_cursor_moving and not @rerender_all
@@ -374,20 +423,27 @@ class Reline::LineEditor
374
423
  else
375
424
  end
376
425
  end
377
- line = modify_lines(whole_lines)[@line_index]
378
426
  if @is_multiline
379
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
380
427
  if finished?
381
428
  # Always rerender on finish because output_modifier_proc may return a different output.
429
+ line = modify_lines(whole_lines)[@line_index]
430
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
382
431
  render_partial(prompt, prompt_width, line, @first_line_started_from)
432
+ move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
383
433
  scroll_down(1)
384
434
  Reline::IOGate.move_cursor_column(0)
385
435
  Reline::IOGate.erase_after_cursor
386
436
  elsif not rendered
387
- render_partial(prompt, prompt_width, line, @first_line_started_from)
437
+ unless @in_pasting
438
+ line = modify_lines(whole_lines)[@line_index]
439
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
440
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
441
+ end
388
442
  end
389
443
  @buffer_of_lines[@line_index] = @line
444
+ @rest_height = 0 if @scroll_partial_screen
390
445
  else
446
+ line = modify_lines(whole_lines)[@line_index]
391
447
  render_partial(prompt, prompt_width, line, 0)
392
448
  if finished?
393
449
  scroll_down(1)
@@ -430,13 +486,13 @@ class Reline::LineEditor
430
486
  end
431
487
  end
432
488
 
433
- private def rerender_added_newline
489
+ private def rerender_added_newline(prompt, prompt_width)
434
490
  scroll_down(1)
435
- new_lines = whole_lines(index: @previous_line_index, line: @line)
436
- prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
437
491
  @buffer_of_lines[@previous_line_index] = @line
438
492
  @line = @buffer_of_lines[@line_index]
439
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
493
+ unless @in_pasting
494
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
495
+ end
440
496
  @cursor = @cursor_max = calculate_width(@line)
441
497
  @byte_pointer = @line.bytesize
442
498
  @highest_in_all += @highest_in_this
@@ -456,7 +512,7 @@ class Reline::LineEditor
456
512
  calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
457
513
  end
458
514
  first_line_diff = new_first_line_started_from - @first_line_started_from
459
- new_cursor, _, new_started_from, _ = calculate_nearest_cursor(@line, @cursor, @started_from, @byte_pointer, false)
515
+ new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
460
516
  new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
461
517
  calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
462
518
  @previous_line_index = nil
@@ -470,6 +526,8 @@ class Reline::LineEditor
470
526
  @first_line_started_from = new_first_line_started_from
471
527
  @started_from = new_started_from
472
528
  @cursor = new_cursor
529
+ @cursor_max = new_cursor_max
530
+ @byte_pointer = new_byte_pointer
473
531
  move_cursor_down(first_line_diff + @started_from)
474
532
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
475
533
  false
@@ -543,7 +601,13 @@ class Reline::LineEditor
543
601
  new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
544
602
  end
545
603
  new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
546
- if back > old_highest_in_all
604
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
605
+ if @scroll_partial_screen
606
+ move_cursor_up(@first_line_started_from + @started_from)
607
+ scroll_down(@screen_height - 1)
608
+ move_cursor_up(@screen_height)
609
+ Reline::IOGate.move_cursor_column(0)
610
+ elsif back > old_highest_in_all
547
611
  scroll_down(back - 1)
548
612
  move_cursor_up(back - 1)
549
613
  elsif back < old_highest_in_all
@@ -555,7 +619,6 @@ class Reline::LineEditor
555
619
  end
556
620
  move_cursor_up(old_highest_in_all - 1)
557
621
  end
558
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
559
622
  render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
560
623
  if @prompt_proc
561
624
  prompt = prompt_list[@line_index]
@@ -576,7 +639,6 @@ class Reline::LineEditor
576
639
 
577
640
  private def render_whole_lines(lines, prompt, prompt_width)
578
641
  rendered_height = 0
579
- @output.write "\e[0m" # clear character decorations
580
642
  modify_lines(lines).each_with_index do |line, index|
581
643
  if prompt.is_a?(Array)
582
644
  line_prompt = prompt[index]
@@ -645,21 +707,20 @@ class Reline::LineEditor
645
707
  cursor_up_from_last_line = height - 1 - @started_from
646
708
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
647
709
  end
710
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
711
+ @output.write "\e[0m" # clear character decorations
712
+ end
648
713
  visual_lines.each_with_index do |line, index|
649
714
  Reline::IOGate.move_cursor_column(0)
650
715
  if line.nil?
651
716
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
652
- # reaches the end of line
653
- if Reline::IOGate.win?
654
- # A newline is automatically inserted if a character is rendered at
655
- # eol on command prompt.
656
- else
657
- # When the cursor is at the end of the line and erases characters
658
- # after the cursor, some terminals delete the character at the
659
- # cursor position.
660
- move_cursor_down(1)
661
- Reline::IOGate.move_cursor_column(0)
662
- end
717
+ # Reaches the end of line.
718
+ #
719
+ # When the cursor is at the end of the line and erases characters
720
+ # after the cursor, some terminals delete the character at the
721
+ # cursor position.
722
+ move_cursor_down(1)
723
+ Reline::IOGate.move_cursor_column(0)
663
724
  else
664
725
  Reline::IOGate.erase_after_cursor
665
726
  move_cursor_down(1)
@@ -668,10 +729,6 @@ class Reline::LineEditor
668
729
  next
669
730
  end
670
731
  @output.write line
671
- if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
672
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
673
- @rest_height -= 1 if @rest_height > 0
674
- end
675
732
  @output.flush
676
733
  if @first_prompt
677
734
  @first_prompt = false
@@ -1063,7 +1120,7 @@ class Reline::LineEditor
1063
1120
  unless completion_occurs
1064
1121
  @completion_state = CompletionState::NORMAL
1065
1122
  end
1066
- if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1123
+ if not @in_pasting and @just_cursor_moving.nil?
1067
1124
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1068
1125
  @just_cursor_moving = true
1069
1126
  elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
@@ -1112,6 +1169,7 @@ class Reline::LineEditor
1112
1169
  new_lines = whole_lines
1113
1170
  end
1114
1171
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1172
+ new_indent = @cursor_max if new_indent&.> @cursor_max
1115
1173
  if new_indent&.>= 0
1116
1174
  md = new_lines[@line_index].match(/\A */)
1117
1175
  prev_indent = md[0].count(' ')
@@ -1312,14 +1370,14 @@ class Reline::LineEditor
1312
1370
  cursor_line = @line.byteslice(0, @byte_pointer)
1313
1371
  insert_new_line(cursor_line, next_line)
1314
1372
  @cursor = 0
1315
- @check_new_auto_indent = true
1373
+ @check_new_auto_indent = true unless @in_pasting
1316
1374
  end
1317
1375
  end
1318
1376
 
1319
1377
  private def ed_unassigned(key) end # do nothing
1320
1378
 
1321
1379
  private def process_insert(force: false)
1322
- return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1380
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1323
1381
  width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1324
1382
  bytesize = @continuous_insertion_buffer.bytesize
1325
1383
  if @cursor == @cursor_max
@@ -1354,7 +1412,7 @@ class Reline::LineEditor
1354
1412
  str = key.chr
1355
1413
  bytesize = 1
1356
1414
  end
1357
- if Reline::IOGate.in_pasting?
1415
+ if @in_pasting
1358
1416
  @continuous_insertion_buffer << str
1359
1417
  return
1360
1418
  elsif not @continuous_insertion_buffer.empty?
@@ -1366,7 +1424,12 @@ class Reline::LineEditor
1366
1424
  else
1367
1425
  @line = byteinsert(@line, @byte_pointer, str)
1368
1426
  end
1427
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1369
1428
  @byte_pointer += bytesize
1429
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1430
+ if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1431
+ width = 0
1432
+ end
1370
1433
  @cursor += width
1371
1434
  @cursor_max += width
1372
1435
  end
@@ -1578,9 +1641,11 @@ class Reline::LineEditor
1578
1641
  searcher = generate_searcher
1579
1642
  searcher.resume(key)
1580
1643
  @searching_prompt = "(reverse-i-search)`': "
1644
+ termination_keys = ["\C-j".ord]
1645
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1581
1646
  @waiting_proc = ->(k) {
1582
1647
  case k
1583
- when "\C-j".ord
1648
+ when *termination_keys
1584
1649
  if @history_pointer
1585
1650
  buffer = Reline::HISTORY[@history_pointer]
1586
1651
  else
@@ -1599,6 +1664,8 @@ class Reline::LineEditor
1599
1664
  @waiting_proc = nil
1600
1665
  @cursor_max = calculate_width(@line)
1601
1666
  @cursor = @byte_pointer = 0
1667
+ @rerender_all = true
1668
+ @cached_prompt_list = nil
1602
1669
  searcher.resume(-1)
1603
1670
  when "\C-g".ord
1604
1671
  if @is_multiline
@@ -1642,6 +1709,8 @@ class Reline::LineEditor
1642
1709
  @waiting_proc = nil
1643
1710
  @cursor_max = calculate_width(@line)
1644
1711
  @cursor = @byte_pointer = 0
1712
+ @rerender_all = true
1713
+ @cached_prompt_list = nil
1645
1714
  searcher.resume(-1)
1646
1715
  end
1647
1716
  end
@@ -1694,7 +1763,7 @@ class Reline::LineEditor
1694
1763
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1695
1764
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1696
1765
  @line_index = line_no
1697
- @line = @buffer_of_lines.last
1766
+ @line = @buffer_of_lines[@line_index]
1698
1767
  @rerender_all = true
1699
1768
  else
1700
1769
  @line = Reline::HISTORY[@history_pointer]
@@ -1742,7 +1811,7 @@ class Reline::LineEditor
1742
1811
  @line_index = line_no
1743
1812
  end
1744
1813
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1745
- @line = @buffer_of_lines.last
1814
+ @line = @buffer_of_lines[@line_index]
1746
1815
  @rerender_all = true
1747
1816
  else
1748
1817
  if @history_pointer.nil? and substr.empty?
@@ -1934,6 +2003,7 @@ class Reline::LineEditor
1934
2003
  @cursor = 0
1935
2004
  end
1936
2005
  end
2006
+ alias_method :kill_line, :em_kill_line
1937
2007
 
1938
2008
  private def em_delete(key)
1939
2009
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1984,6 +2054,7 @@ class Reline::LineEditor
1984
2054
  @byte_pointer += yanked.bytesize
1985
2055
  end
1986
2056
  end
2057
+ alias_method :yank, :em_yank
1987
2058
 
1988
2059
  private def em_yank_pop(key)
1989
2060
  yanked, prev_yank = @kill_ring.yank_pop
@@ -2000,6 +2071,7 @@ class Reline::LineEditor
2000
2071
  @byte_pointer += yanked.bytesize
2001
2072
  end
2002
2073
  end
2074
+ alias_method :yank_pop, :em_yank_pop
2003
2075
 
2004
2076
  private def ed_clear_screen(key)
2005
2077
  @cleared = true
@@ -2130,9 +2202,10 @@ class Reline::LineEditor
2130
2202
  @byte_pointer -= byte_size
2131
2203
  @cursor -= width
2132
2204
  @cursor_max -= width
2133
- @kill_ring.append(deleted)
2205
+ @kill_ring.append(deleted, true)
2134
2206
  end
2135
2207
  end
2208
+ alias_method :unix_word_rubout, :em_kill_region
2136
2209
 
2137
2210
  private def copy_for_vi(text)
2138
2211
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@@ -2157,7 +2230,7 @@ class Reline::LineEditor
2157
2230
 
2158
2231
  private def vi_next_word(key, arg: 1)
2159
2232
  if @line.bytesize > @byte_pointer
2160
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2233
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2161
2234
  @byte_pointer += byte_size
2162
2235
  @cursor += width
2163
2236
  end
@@ -2285,6 +2358,7 @@ class Reline::LineEditor
2285
2358
  end
2286
2359
 
2287
2360
  private def vi_change_meta(key, arg: 1)
2361
+ @drop_terminate_spaces = true
2288
2362
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2289
2363
  if byte_pointer_diff > 0
2290
2364
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2296,6 +2370,7 @@ class Reline::LineEditor
2296
2370
  @cursor_max -= cursor_diff.abs
2297
2371
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2298
2372
  @config.editing_mode = :vi_insert
2373
+ @drop_terminate_spaces = false
2299
2374
  }
2300
2375
  @waiting_operator_vi_arg = arg
2301
2376
  end
@@ -2351,6 +2426,9 @@ class Reline::LineEditor
2351
2426
  width = Reline::Unicode.get_mbchar_width(mbchar)
2352
2427
  @cursor_max -= width
2353
2428
  if @cursor > 0 and @cursor >= @cursor_max
2429
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2430
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2431
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2354
2432
  @byte_pointer -= byte_size
2355
2433
  @cursor -= width
2356
2434
  end
@@ -2384,11 +2462,23 @@ class Reline::LineEditor
2384
2462
 
2385
2463
  private def vi_histedit(key)
2386
2464
  path = Tempfile.open { |fp|
2387
- fp.write @line
2465
+ if @is_multiline
2466
+ fp.write whole_lines.join("\n")
2467
+ else
2468
+ fp.write @line
2469
+ end
2388
2470
  fp.path
2389
2471
  }
2390
2472
  system("#{ENV['EDITOR']} #{path}")
2391
- @line = File.read(path)
2473
+ if @is_multiline
2474
+ @buffer_of_lines = File.read(path).split("\n")
2475
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2476
+ @line_index = 0
2477
+ @line = @buffer_of_lines[@line_index]
2478
+ @rerender_all = true
2479
+ else
2480
+ @line = File.read(path)
2481
+ end
2392
2482
  finish
2393
2483
  end
2394
2484
 
@@ -2447,7 +2537,7 @@ class Reline::LineEditor
2447
2537
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2448
2538
  before = @line.byteslice(0, @byte_pointer)
2449
2539
  remaining_point = @byte_pointer + byte_size
2450
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2540
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2451
2541
  @line = before + k.chr + after
2452
2542
  @cursor_max = calculate_width(@line)
2453
2543
  @waiting_proc = nil
@@ -2458,7 +2548,7 @@ class Reline::LineEditor
2458
2548
  end
2459
2549
  before = @line.byteslice(0, @byte_pointer)
2460
2550
  remaining_point = @byte_pointer + byte_size
2461
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2551
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2462
2552
  replaced = k.chr * arg
2463
2553
  @line = before + replaced + after
2464
2554
  @byte_pointer += replaced.bytesize
@@ -108,6 +108,7 @@ class Reline::Unicode
108
108
  end
109
109
  m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
110
110
  case
111
+ when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
111
112
  when m[:width_2_1], m[:width_2_2] then 2
112
113
  when m[:width_3] then 3
113
114
  when m[:width_0] then 0
@@ -458,8 +459,8 @@ class Reline::Unicode
458
459
  [byte_size, width]
459
460
  end
460
461
 
461
- def self.vi_forward_word(line, byte_pointer)
462
- if (line.bytesize - 1) > byte_pointer
462
+ def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false)
463
+ if line.bytesize > byte_pointer
463
464
  size = get_next_mbchar_size(line, byte_pointer)
464
465
  mbchar = line.byteslice(byte_pointer, size)
465
466
  if mbchar =~ /\w/
@@ -474,7 +475,7 @@ class Reline::Unicode
474
475
  else
475
476
  return [0, 0]
476
477
  end
477
- while (line.bytesize - 1) > (byte_pointer + byte_size)
478
+ while line.bytesize > (byte_pointer + byte_size)
478
479
  size = get_next_mbchar_size(line, byte_pointer + byte_size)
479
480
  mbchar = line.byteslice(byte_pointer + byte_size, size)
480
481
  case started_by
@@ -488,7 +489,8 @@ class Reline::Unicode
488
489
  width += get_mbchar_width(mbchar)
489
490
  byte_size += size
490
491
  end
491
- while (line.bytesize - 1) > (byte_pointer + byte_size)
492
+ return [byte_size, width] if drop_terminate_spaces
493
+ while line.bytesize > (byte_pointer + byte_size)
492
494
  size = get_next_mbchar_size(line, byte_pointer + byte_size)
493
495
  mbchar = line.byteslice(byte_pointer + byte_size, size)
494
496
  break if mbchar =~ /\S/
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.1.10'
2
+ VERSION = '0.2.4'
3
3
  end
@@ -9,6 +9,10 @@ class Reline::Windows
9
9
  true
10
10
  end
11
11
 
12
+ def self.win_legacy_console?
13
+ @@legacy_console
14
+ end
15
+
12
16
  RAW_KEYSTROKE_CONFIG = {
13
17
  [224, 72] => :ed_prev_history, # ↑
14
18
  [224, 80] => :ed_next_history, # ↓
@@ -94,6 +98,26 @@ class Reline::Windows
94
98
  @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
95
99
  @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
96
100
 
101
+ @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
102
+ @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
103
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
104
+
105
+ private_class_method def self.getconsolemode
106
+ mode = "\000\000\000\000"
107
+ @@GetConsoleMode.call(@@hConsoleHandle, mode)
108
+ mode.unpack1('L')
109
+ end
110
+
111
+ private_class_method def self.setconsolemode(mode)
112
+ @@SetConsoleMode.call(@@hConsoleHandle, mode)
113
+ end
114
+
115
+ @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
116
+ #if @@legacy_console
117
+ # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
118
+ # @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
119
+ #end
120
+
97
121
  @@input_buf = []
98
122
  @@output_buf = []
99
123
 
@@ -258,6 +282,7 @@ class Reline::Windows
258
282
  cursor = csbi[4, 4].unpack('L').first
259
283
  written = 0.chr * 4
260
284
  @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
285
+ @@FillConsoleOutputAttribute.call(@@hConsoleHandle, 0, get_screen_size.last - cursor_pos.x, cursor, written)
261
286
  end
262
287
 
263
288
  def self.scroll_down(val)
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2009, Park Heesob
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of Park Heesob nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-20 00:00:00.000000000 Z
11
+ date: 2021-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -109,6 +109,7 @@ files:
109
109
  - lib/reline/unicode/east_asian_width.rb
110
110
  - lib/reline/version.rb
111
111
  - lib/reline/windows.rb
112
+ - license_of_rb-readline
112
113
  homepage: https://github.com/ruby/reline
113
114
  licenses:
114
115
  - Ruby