reline 0.1.10 → 0.2.4

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