reline 0.1.8 → 0.2.2

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: 89f055454b502262f43495a2d8de12ba033259287a4c09e7c93afeb0462c9c33
4
- data.tar.gz: 6c6f081e14d699faed9ac7ba08001dac978318535f58458fd2c777db56b57b50
3
+ metadata.gz: 5864c77904d813f20ffdb6963ba8e2297e84c3e71b9c94c2a55528ccf73b47da
4
+ data.tar.gz: c0bf68a1988228c3ad046e3e6658b79d22b59a98792d2c9666ecf92d9bf74a2d
5
5
  SHA512:
6
- metadata.gz: 6bd17793312ff9f2996ce08ebada091005c7efd9ff1d0ef007ea982ee5e74ee03a83fb0bb7c975c6da747db712a3837d14c108fd7de087094f1db37e051876aa
7
- data.tar.gz: 148dd6767b1aae9a295d334445957b64e6f6a46b8941a10517e5d4567a29eac369fe3843c6c8c1bb5fd66d1165c82988316b030eb05080517bb7105bc5174281
6
+ metadata.gz: 3ef8bf25d8412ee409a2d5bf13568d50befb5c8a8bbd64b7f82c8649807a42e8c822950d8f76c520ff464ddc91e9c80f1ade09f902f671b5a678631b606ebfd6
7
+ data.tar.gz: 32a2f60967b403860f8efbe92163e0f564e3cff553b700db6a9ddaf8b8a44dd8f30c64db66ff793a95cd7ad2ab8348dd3548d9f2e2ed88ca3f50861d529faec4
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.
@@ -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
 
@@ -44,6 +43,7 @@ module Reline
44
43
  self.output = STDOUT
45
44
  yield self
46
45
  @completion_quote_character = nil
46
+ @bracketed_paste_finished = false
47
47
  end
48
48
 
49
49
  def encoding
@@ -199,7 +199,11 @@ module Reline
199
199
 
200
200
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
201
201
  if ENV['RELINE_STDERR_TTY']
202
- $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
202
+ if Reline::IOGate.win?
203
+ $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
204
+ else
205
+ $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
206
+ end
203
207
  $stderr.sync = true
204
208
  $stderr.puts "Reline is used by #{Process.pid}"
205
209
  end
@@ -243,6 +247,10 @@ module Reline
243
247
  line_editor.input_key(c)
244
248
  line_editor.rerender
245
249
  }
250
+ if @bracketed_paste_finished
251
+ line_editor.rerender_all
252
+ @bracketed_paste_finished = false
253
+ end
246
254
  }
247
255
  if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
248
256
  prev_pasting_state = false
@@ -275,8 +283,13 @@ module Reline
275
283
  buffer = []
276
284
  loop do
277
285
  c = Reline::IOGate.getc
278
- buffer << c
279
- result = key_stroke.match_status(buffer)
286
+ if c == -1
287
+ result = :unmatched
288
+ @bracketed_paste_finished = true
289
+ else
290
+ buffer << c
291
+ result = key_stroke.match_status(buffer)
292
+ end
280
293
  case result
281
294
  when :matched
282
295
  expanded = key_stroke.expand(buffer).map{ |expanded_c|
@@ -342,9 +355,14 @@ module Reline
342
355
  end
343
356
  end
344
357
 
358
+ def ambiguous_width
359
+ may_req_ambiguous_char_width unless defined? @ambiguous_width
360
+ @ambiguous_width
361
+ end
362
+
345
363
  private def may_req_ambiguous_char_width
346
364
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
347
- return if ambiguous_width
365
+ return if @ambiguous_width
348
366
  Reline::IOGate.move_cursor_column(0)
349
367
  begin
350
368
  output.write "\u{25bd}"
@@ -1,4 +1,5 @@
1
1
  require 'io/console'
2
+ require 'timeout'
2
3
 
3
4
  class Reline::ANSI
4
5
  def self.encoding
@@ -67,7 +68,7 @@ class Reline::ANSI
67
68
  end
68
69
 
69
70
  @@buf = []
70
- def self.getc
71
+ def self.inner_getc
71
72
  unless @@buf.empty?
72
73
  return @@buf.shift
73
74
  end
@@ -80,8 +81,48 @@ class Reline::ANSI
80
81
  nil
81
82
  end
82
83
 
84
+ @@in_bracketed_paste_mode = false
85
+ START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
86
+ END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
87
+ def self.getc_with_bracketed_paste
88
+ buffer = String.new(encoding: Encoding::ASCII_8BIT)
89
+ buffer << inner_getc
90
+ while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
91
+ if START_BRACKETED_PASTE == buffer
92
+ @@in_bracketed_paste_mode = true
93
+ return inner_getc
94
+ elsif END_BRACKETED_PASTE == buffer
95
+ @@in_bracketed_paste_mode = false
96
+ ungetc(-1)
97
+ return inner_getc
98
+ end
99
+ begin
100
+ succ_c = nil
101
+ Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
102
+ succ_c = inner_getc
103
+ }
104
+ rescue Timeout::Error
105
+ break
106
+ else
107
+ buffer << succ_c
108
+ end
109
+ end
110
+ buffer.bytes.reverse_each do |ch|
111
+ ungetc ch
112
+ end
113
+ inner_getc
114
+ end
115
+
116
+ def self.getc
117
+ if Reline.core.config.enable_bracketed_paste
118
+ getc_with_bracketed_paste
119
+ else
120
+ inner_getc
121
+ end
122
+ end
123
+
83
124
  def self.in_pasting?
84
- not Reline::IOGate.empty_buffer?
125
+ @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
85
126
  end
86
127
 
87
128
  def self.empty_buffer?
@@ -131,7 +172,7 @@ class Reline::ANSI
131
172
 
132
173
  def self.cursor_pos
133
174
  begin
134
- res = ''
175
+ res = +''
135
176
  m = nil
136
177
  @@input.raw do |stdin|
137
178
  @@output << "\e[6n"
@@ -34,9 +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
+ enable-bracketed-paste
41
+ isearch-terminators
40
42
  }
41
43
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
42
44
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -54,8 +56,8 @@ class Reline::Config
54
56
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
55
57
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
56
58
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
- @vi_cmd_mode_icon = '(cmd)'
58
- @vi_ins_mode_icon = '(ins)'
59
+ @vi_cmd_mode_string = '(cmd)'
60
+ @vi_ins_mode_string = '(ins)'
59
61
  @emacs_mode_string = '@'
60
62
  # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
61
63
  @history_size = -1 # unlimited
@@ -237,7 +239,7 @@ class Reline::Config
237
239
  when 'completion-query-items'
238
240
  @completion_query_items = value.to_i
239
241
  when 'isearch-terminators'
240
- @isearch_terminators = instance_eval(value)
242
+ @isearch_terminators = retrieve_string(value)
241
243
  when 'editing-mode'
242
244
  case value
243
245
  when 'emacs'
@@ -268,9 +270,9 @@ class Reline::Config
268
270
  @show_mode_in_prompt = false
269
271
  end
270
272
  when 'vi-cmd-mode-string'
271
- @vi_cmd_mode_icon = retrieve_string(value)
273
+ @vi_cmd_mode_string = retrieve_string(value)
272
274
  when 'vi-ins-mode-string'
273
- @vi_ins_mode_icon = retrieve_string(value)
275
+ @vi_ins_mode_string = retrieve_string(value)
274
276
  when 'emacs-mode-string'
275
277
  @emacs_mode_string = retrieve_string(value)
276
278
  when *VARIABLE_NAMES then
@@ -307,7 +307,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
307
307
  # 152 M-^X
308
308
  :ed_unassigned,
309
309
  # 153 M-^Y
310
- :ed_unassigned,
310
+ :em_yank_pop,
311
311
  # 154 M-^Z
312
312
  :ed_unassigned,
313
313
  # 155 M-^[
@@ -1,4 +1,6 @@
1
1
  class Reline::KillRing
2
+ include Enumerable
3
+
2
4
  module State
3
5
  FRESH = :fresh
4
6
  CONTINUED = :continued
@@ -110,4 +112,14 @@ class Reline::KillRing
110
112
  nil
111
113
  end
112
114
  end
115
+
116
+ def each
117
+ start = head = @ring.head
118
+ loop do
119
+ break if head.nil?
120
+ yield head.str
121
+ head = head.backward
122
+ break if head == start
123
+ end
124
+ end
113
125
  end
@@ -50,6 +50,8 @@ class Reline::LineEditor
50
50
  CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
51
51
  MenuInfo = Struct.new('MenuInfo', :target, :list)
52
52
 
53
+ PROMPT_LIST_CACHE_TIMEOUT = 0.5
54
+
53
55
  def initialize(config, encoding)
54
56
  @config = config
55
57
  @completion_append_character = ''
@@ -59,11 +61,33 @@ class Reline::LineEditor
59
61
  def simplified_rendering?
60
62
  if finished?
61
63
  false
64
+ elsif @just_cursor_moving and not @rerender_all
65
+ true
62
66
  else
63
67
  not @rerender_all and not finished? and Reline::IOGate.in_pasting?
64
68
  end
65
69
  end
66
70
 
71
+ private def check_mode_string
72
+ mode_string = nil
73
+ if @config.show_mode_in_prompt
74
+ if @config.editing_mode_is?(:vi_command)
75
+ mode_string = @config.vi_cmd_mode_string
76
+ elsif @config.editing_mode_is?(:vi_insert)
77
+ mode_string = @config.vi_ins_mode_string
78
+ elsif @config.editing_mode_is?(:emacs)
79
+ mode_string = @config.emacs_mode_string
80
+ else
81
+ mode_string = '?'
82
+ end
83
+ end
84
+ if mode_string != @prev_mode_string
85
+ @rerender_all = true
86
+ end
87
+ @prev_mode_string = mode_string
88
+ mode_string
89
+ end
90
+
67
91
  private def check_multiline_prompt(buffer, prompt)
68
92
  if @vi_arg
69
93
  prompt = "(arg: #{@vi_arg}) "
@@ -74,39 +98,44 @@ class Reline::LineEditor
74
98
  else
75
99
  prompt = @prompt
76
100
  end
77
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
101
+ if simplified_rendering?
102
+ mode_string = check_mode_string
103
+ prompt = mode_string + prompt if mode_string
104
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
105
+ end
78
106
  if @prompt_proc
79
- prompt_list = @prompt_proc.(buffer)
80
- prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
81
- if @config.show_mode_in_prompt
82
- if @config.editing_mode_is?(:vi_command)
83
- mode_icon = @config.vi_cmd_mode_icon
84
- elsif @config.editing_mode_is?(:vi_insert)
85
- mode_icon = @config.vi_ins_mode_icon
86
- elsif @config.editing_mode_is?(:emacs)
87
- mode_icon = @config.emacs_mode_string
88
- else
89
- mode_icon = '?'
107
+ use_cached_prompt_list = false
108
+ if @cached_prompt_list
109
+ if @just_cursor_moving
110
+ use_cached_prompt_list = true
111
+ elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
112
+ use_cached_prompt_list = true
90
113
  end
91
- prompt_list.map!{ |pr| mode_icon + pr }
92
114
  end
115
+ use_cached_prompt_list = false if @rerender_all
116
+ if use_cached_prompt_list
117
+ prompt_list = @cached_prompt_list
118
+ else
119
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
120
+ @prompt_cache_time = Time.now.to_f
121
+ end
122
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
123
+ mode_string = check_mode_string
124
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
93
125
  prompt = prompt_list[@line_index]
126
+ prompt = prompt_list[0] if prompt.nil?
127
+ prompt = prompt_list.last if prompt.nil?
128
+ if buffer.size > prompt_list.size
129
+ (buffer.size - prompt_list.size).times do
130
+ prompt_list << prompt_list.last
131
+ end
132
+ end
94
133
  prompt_width = calculate_width(prompt, true)
95
134
  [prompt, prompt_width, prompt_list]
96
135
  else
136
+ mode_string = check_mode_string
137
+ prompt = mode_string + prompt if mode_string
97
138
  prompt_width = calculate_width(prompt, true)
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 = '?'
107
- end
108
- prompt = mode_icon + prompt
109
- end
110
139
  [prompt, prompt_width, nil]
111
140
  end
112
141
  end
@@ -114,6 +143,7 @@ class Reline::LineEditor
114
143
  def reset(prompt = '', encoding:)
115
144
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
116
145
  @screen_size = Reline::IOGate.get_screen_size
146
+ @screen_height = @screen_size.first
117
147
  reset_variables(prompt, encoding: encoding)
118
148
  @old_trap = Signal.trap('SIGINT') {
119
149
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
@@ -123,6 +153,7 @@ class Reline::LineEditor
123
153
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
124
154
  old_screen_size = @screen_size
125
155
  @screen_size = Reline::IOGate.get_screen_size
156
+ @screen_height = @screen_size.first
126
157
  if old_screen_size.last < @screen_size.last # columns increase
127
158
  @rerender_all = true
128
159
  rerender
@@ -174,11 +205,12 @@ class Reline::LineEditor
174
205
  @cleared = false
175
206
  @rerender_all = false
176
207
  @history_pointer = nil
177
- @kill_ring = Reline::KillRing.new
208
+ @kill_ring ||= Reline::KillRing.new
178
209
  @vi_clipboard = ''
179
210
  @vi_arg = nil
180
211
  @waiting_proc = nil
181
212
  @waiting_operator_proc = nil
213
+ @waiting_operator_vi_arg = nil
182
214
  @completion_journey_data = nil
183
215
  @completion_state = CompletionState::NORMAL
184
216
  @perfect_matched = nil
@@ -186,8 +218,15 @@ class Reline::LineEditor
186
218
  @first_prompt = true
187
219
  @searching_prompt = nil
188
220
  @first_char = true
221
+ @add_newline_to_end_of_buffer = false
222
+ @just_cursor_moving = nil
223
+ @cached_prompt_list = nil
224
+ @prompt_cache_time = nil
189
225
  @eof = false
190
226
  @continuous_insertion_buffer = String.new(encoding: @encoding)
227
+ @scroll_partial_screen = nil
228
+ @prev_mode_string = nil
229
+ @drop_terminate_spaces = false
191
230
  reset_line
192
231
  end
193
232
 
@@ -232,6 +271,7 @@ class Reline::LineEditor
232
271
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
233
272
  @previous_line_index = @line_index
234
273
  @line_index += 1
274
+ @just_cursor_moving = false
235
275
  end
236
276
 
237
277
  private def calculate_height_by_width(width)
@@ -272,28 +312,28 @@ class Reline::LineEditor
272
312
  end
273
313
  end
274
314
 
275
- private def calculate_nearest_cursor
276
- @cursor_max = calculate_width(line)
315
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
316
+ new_cursor_max = calculate_width(line_to_calc)
277
317
  new_cursor = 0
278
318
  new_byte_pointer = 0
279
319
  height = 1
280
320
  max_width = @screen_size.last
281
321
  if @config.editing_mode_is?(:vi_command)
282
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
322
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
283
323
  if last_byte_size > 0
284
- last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
324
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
285
325
  last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
286
- cursor_max = @cursor_max - last_width
326
+ end_of_line_cursor = new_cursor_max - last_width
287
327
  else
288
- cursor_max = @cursor_max
328
+ end_of_line_cursor = new_cursor_max
289
329
  end
290
330
  else
291
- cursor_max = @cursor_max
331
+ end_of_line_cursor = new_cursor_max
292
332
  end
293
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
333
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
294
334
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
295
335
  now = new_cursor + mbchar_width
296
- if now > cursor_max or now > @cursor
336
+ if now > end_of_line_cursor or now > cursor
297
337
  break
298
338
  end
299
339
  new_cursor += mbchar_width
@@ -302,13 +342,20 @@ class Reline::LineEditor
302
342
  end
303
343
  new_byte_pointer += gc.bytesize
304
344
  end
305
- @started_from = height - 1
306
- @cursor = new_cursor
307
- @byte_pointer = new_byte_pointer
345
+ new_started_from = height - 1
346
+ if update
347
+ @cursor = new_cursor
348
+ @cursor_max = new_cursor_max
349
+ @started_from = new_started_from
350
+ @byte_pointer = new_byte_pointer
351
+ else
352
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
353
+ end
308
354
  end
309
355
 
310
356
  def rerender_all
311
357
  @rerender_all = true
358
+ process_insert(force: true)
312
359
  rerender
313
360
  end
314
361
 
@@ -317,168 +364,54 @@ class Reline::LineEditor
317
364
  if @menu_info
318
365
  scroll_down(@highest_in_all - @first_line_started_from)
319
366
  @rerender_all = true
320
- @menu_info.list.sort!.each do |item|
321
- Reline::IOGate.move_cursor_column(0)
322
- @output.write item
323
- @output.flush
324
- scroll_down(1)
325
- end
326
- scroll_down(@highest_in_all - 1)
327
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
367
+ end
368
+ if @menu_info
369
+ show_menu
328
370
  @menu_info = nil
329
371
  end
330
372
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
331
373
  if @cleared
332
- Reline::IOGate.clear_screen
374
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
333
375
  @cleared = false
334
- back = 0
335
- modify_lines(whole_lines).each_with_index do |line, index|
336
- if @prompt_proc
337
- pr = prompt_list[index]
338
- height = render_partial(pr, calculate_width(pr), line, false)
339
- else
340
- height = render_partial(prompt, prompt_width, line, false)
341
- end
342
- if index < (@buffer_of_lines.size - 1)
343
- move_cursor_down(height)
344
- back += height
345
- end
346
- end
347
- move_cursor_up(back)
348
- move_cursor_down(@first_line_started_from + @started_from)
349
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
350
376
  return
351
377
  end
352
378
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
353
379
  # FIXME: end of logical line sometimes breaks
354
- if @previous_line_index or new_highest_in_this != @highest_in_this
355
- if @previous_line_index
356
- new_lines = whole_lines(index: @previous_line_index, line: @line)
357
- else
358
- new_lines = whole_lines
359
- end
360
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
361
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
362
- diff = all_height - @highest_in_all
363
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
364
- if diff > 0
365
- scroll_down(diff)
366
- move_cursor_up(all_height - 1)
367
- elsif diff < 0
368
- (-diff).times do
369
- Reline::IOGate.move_cursor_column(0)
370
- Reline::IOGate.erase_after_cursor
371
- move_cursor_up(1)
372
- end
373
- move_cursor_up(all_height - 1)
380
+ rendered = false
381
+ if @add_newline_to_end_of_buffer
382
+ rerender_added_newline
383
+ @add_newline_to_end_of_buffer = false
384
+ else
385
+ if @just_cursor_moving and not @rerender_all
386
+ rendered = just_move_cursor
387
+ @just_cursor_moving = false
388
+ return
389
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
390
+ rerender_changed_current_line
391
+ @previous_line_index = nil
392
+ rendered = true
393
+ elsif @rerender_all
394
+ rerender_all_lines
395
+ @rerender_all = false
396
+ rendered = true
374
397
  else
375
- move_cursor_up(all_height - 1)
376
- end
377
- @highest_in_all = all_height
378
- back = 0
379
- modify_lines(new_lines).each_with_index do |line, index|
380
- if @prompt_proc
381
- prompt = prompt_list[index]
382
- prompt_width = calculate_width(prompt, true)
383
- end
384
- height = render_partial(prompt, prompt_width, line, false)
385
- if index < (new_lines.size - 1)
386
- scroll_down(1)
387
- back += height
388
- else
389
- back += height - 1
390
- end
391
- end
392
- move_cursor_up(back)
393
- if @previous_line_index
394
- @buffer_of_lines[@previous_line_index] = @line
395
- @line = @buffer_of_lines[@line_index]
396
398
  end
397
- @first_line_started_from =
398
- if @line_index.zero?
399
- 0
400
- else
401
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
402
- end
403
- if @prompt_proc
404
- prompt = prompt_list[@line_index]
405
- prompt_width = calculate_width(prompt, true)
406
- end
407
- move_cursor_down(@first_line_started_from)
408
- calculate_nearest_cursor
409
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
410
- move_cursor_down(@started_from)
411
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
412
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
413
- @previous_line_index = nil
414
- rendered = true
415
- elsif @rerender_all
416
- move_cursor_up(@first_line_started_from + @started_from)
417
- Reline::IOGate.move_cursor_column(0)
418
- back = 0
419
- new_buffer = whole_lines
420
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
421
- new_buffer.each_with_index do |line, index|
422
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
423
- width = prompt_width + calculate_width(line)
424
- height = calculate_height_by_width(width)
425
- back += height
426
- end
427
- if back > @highest_in_all
428
- scroll_down(back - 1)
429
- move_cursor_up(back - 1)
430
- elsif back < @highest_in_all
431
- scroll_down(back)
432
- Reline::IOGate.erase_after_cursor
433
- (@highest_in_all - back - 1).times do
434
- scroll_down(1)
435
- Reline::IOGate.erase_after_cursor
436
- end
437
- move_cursor_up(@highest_in_all - 1)
438
- end
439
- modify_lines(new_buffer).each_with_index do |line, index|
440
- if @prompt_proc
441
- prompt = prompt_list[index]
442
- prompt_width = calculate_width(prompt, true)
443
- end
444
- render_partial(prompt, prompt_width, line, false)
445
- if index < (new_buffer.size - 1)
446
- move_cursor_down(1)
447
- end
448
- end
449
- move_cursor_up(back - 1)
450
- if @prompt_proc
451
- prompt = prompt_list[@line_index]
452
- prompt_width = calculate_width(prompt, true)
453
- end
454
- @highest_in_all = back
455
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
456
- @first_line_started_from =
457
- if @line_index.zero?
458
- 0
459
- else
460
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
461
- end
462
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
463
- move_cursor_down(@first_line_started_from + @started_from)
464
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
465
- @rerender_all = false
466
- rendered = true
467
399
  end
468
400
  line = modify_lines(whole_lines)[@line_index]
469
401
  if @is_multiline
470
402
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
471
403
  if finished?
472
404
  # Always rerender on finish because output_modifier_proc may return a different output.
473
- render_partial(prompt, prompt_width, line)
405
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
474
406
  scroll_down(1)
475
407
  Reline::IOGate.move_cursor_column(0)
476
408
  Reline::IOGate.erase_after_cursor
477
409
  elsif not rendered
478
- render_partial(prompt, prompt_width, line)
410
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
479
411
  end
412
+ @buffer_of_lines[@line_index] = @line
480
413
  else
481
- render_partial(prompt, prompt_width, line)
414
+ render_partial(prompt, prompt_width, line, 0)
482
415
  if finished?
483
416
  scroll_down(1)
484
417
  Reline::IOGate.move_cursor_column(0)
@@ -487,8 +420,239 @@ class Reline::LineEditor
487
420
  end
488
421
  end
489
422
 
490
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
423
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
424
+ if @screen_height < highest_in_all
425
+ old_scroll_partial_screen = @scroll_partial_screen
426
+ if cursor_y == 0
427
+ @scroll_partial_screen = 0
428
+ elsif cursor_y == (highest_in_all - 1)
429
+ @scroll_partial_screen = highest_in_all - @screen_height
430
+ else
431
+ if @scroll_partial_screen
432
+ if cursor_y <= @scroll_partial_screen
433
+ @scroll_partial_screen = cursor_y
434
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
435
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
436
+ end
437
+ else
438
+ if cursor_y > (@screen_height - 1)
439
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
440
+ else
441
+ @scroll_partial_screen = 0
442
+ end
443
+ end
444
+ end
445
+ if @scroll_partial_screen != old_scroll_partial_screen
446
+ @rerender_all = true
447
+ end
448
+ else
449
+ if @scroll_partial_screen
450
+ @rerender_all = true
451
+ end
452
+ @scroll_partial_screen = nil
453
+ end
454
+ end
455
+
456
+ private def rerender_added_newline
457
+ scroll_down(1)
458
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
459
+ prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
460
+ @buffer_of_lines[@previous_line_index] = @line
461
+ @line = @buffer_of_lines[@line_index]
462
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
463
+ @cursor = @cursor_max = calculate_width(@line)
464
+ @byte_pointer = @line.bytesize
465
+ @highest_in_all += @highest_in_this
466
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
467
+ @first_line_started_from += @started_from + 1
468
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
469
+ @previous_line_index = nil
470
+ end
471
+
472
+ def just_move_cursor
473
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
474
+ move_cursor_up(@started_from)
475
+ new_first_line_started_from =
476
+ if @line_index.zero?
477
+ 0
478
+ else
479
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
480
+ end
481
+ first_line_diff = new_first_line_started_from - @first_line_started_from
482
+ 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)
483
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
484
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
485
+ @previous_line_index = nil
486
+ if @rerender_all
487
+ @line = @buffer_of_lines[@line_index]
488
+ rerender_all_lines
489
+ @rerender_all = false
490
+ true
491
+ else
492
+ @line = @buffer_of_lines[@line_index]
493
+ @first_line_started_from = new_first_line_started_from
494
+ @started_from = new_started_from
495
+ @cursor = new_cursor
496
+ @cursor_max = new_cursor_max
497
+ @byte_pointer = new_byte_pointer
498
+ move_cursor_down(first_line_diff + @started_from)
499
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
500
+ false
501
+ end
502
+ end
503
+
504
+ private def rerender_changed_current_line
505
+ if @previous_line_index
506
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
507
+ else
508
+ new_lines = whole_lines
509
+ end
510
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
511
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
512
+ diff = all_height - @highest_in_all
513
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
514
+ if diff > 0
515
+ scroll_down(diff)
516
+ move_cursor_up(all_height - 1)
517
+ elsif diff < 0
518
+ (-diff).times do
519
+ Reline::IOGate.move_cursor_column(0)
520
+ Reline::IOGate.erase_after_cursor
521
+ move_cursor_up(1)
522
+ end
523
+ move_cursor_up(all_height - 1)
524
+ else
525
+ move_cursor_up(all_height - 1)
526
+ end
527
+ @highest_in_all = all_height
528
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
529
+ move_cursor_up(back)
530
+ if @previous_line_index
531
+ @buffer_of_lines[@previous_line_index] = @line
532
+ @line = @buffer_of_lines[@line_index]
533
+ end
534
+ @first_line_started_from =
535
+ if @line_index.zero?
536
+ 0
537
+ else
538
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
539
+ end
540
+ if @prompt_proc
541
+ prompt = prompt_list[@line_index]
542
+ prompt_width = calculate_width(prompt, true)
543
+ end
544
+ move_cursor_down(@first_line_started_from)
545
+ calculate_nearest_cursor
546
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
547
+ move_cursor_down(@started_from)
548
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
549
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
550
+ end
551
+
552
+ private def rerender_all_lines
553
+ move_cursor_up(@first_line_started_from + @started_from)
554
+ Reline::IOGate.move_cursor_column(0)
555
+ back = 0
556
+ new_buffer = whole_lines
557
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
558
+ new_buffer.each_with_index do |line, index|
559
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
560
+ width = prompt_width + calculate_width(line)
561
+ height = calculate_height_by_width(width)
562
+ back += height
563
+ end
564
+ old_highest_in_all = @highest_in_all
565
+ if @line_index.zero?
566
+ new_first_line_started_from = 0
567
+ else
568
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
569
+ end
570
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
571
+ if back > old_highest_in_all
572
+ scroll_down(back - 1)
573
+ move_cursor_up(back - 1)
574
+ elsif back < old_highest_in_all
575
+ scroll_down(back)
576
+ Reline::IOGate.erase_after_cursor
577
+ (old_highest_in_all - back - 1).times do
578
+ scroll_down(1)
579
+ Reline::IOGate.erase_after_cursor
580
+ end
581
+ move_cursor_up(old_highest_in_all - 1)
582
+ end
583
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
584
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
585
+ if @prompt_proc
586
+ prompt = prompt_list[@line_index]
587
+ prompt_width = calculate_width(prompt, true)
588
+ end
589
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
590
+ @highest_in_all = back
591
+ @first_line_started_from = new_first_line_started_from
592
+ @started_from = new_started_from
593
+ if @scroll_partial_screen
594
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
595
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
596
+ else
597
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
598
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
599
+ end
600
+ end
601
+
602
+ private def render_whole_lines(lines, prompt, prompt_width)
603
+ rendered_height = 0
604
+ modify_lines(lines).each_with_index do |line, index|
605
+ if prompt.is_a?(Array)
606
+ line_prompt = prompt[index]
607
+ prompt_width = calculate_width(line_prompt, true)
608
+ else
609
+ line_prompt = prompt
610
+ end
611
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
612
+ if index < (lines.size - 1)
613
+ if @scroll_partial_screen
614
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
615
+ move_cursor_down(1)
616
+ end
617
+ else
618
+ scroll_down(1)
619
+ end
620
+ rendered_height += height
621
+ else
622
+ rendered_height += height - 1
623
+ end
624
+ end
625
+ rendered_height
626
+ end
627
+
628
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
491
629
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
630
+ cursor_up_from_last_line = 0
631
+ # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
632
+ if @scroll_partial_screen
633
+ last_visual_line = this_started_from + (height - 1)
634
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
635
+ if (@scroll_partial_screen - this_started_from) >= height
636
+ # Render nothing because this line is before the screen.
637
+ visual_lines = []
638
+ elsif this_started_from > last_screen_line
639
+ # Render nothing because this line is after the screen.
640
+ visual_lines = []
641
+ else
642
+ deleted_lines_before_screen = []
643
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
644
+ # A part of visual lines are before the screen.
645
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
646
+ deleted_lines_before_screen.compact!
647
+ end
648
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
649
+ # A part of visual lines are after the screen.
650
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
651
+ end
652
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
653
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
654
+ end
655
+ end
492
656
  if with_control
493
657
  if height > @highest_in_this
494
658
  diff = height - @highest_in_this
@@ -502,23 +666,23 @@ class Reline::LineEditor
502
666
  @highest_in_this = height
503
667
  end
504
668
  move_cursor_up(@started_from)
669
+ cursor_up_from_last_line = height - 1 - @started_from
505
670
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
506
671
  end
507
- Reline::IOGate.move_cursor_column(0)
672
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
673
+ @output.write "\e[0m" # clear character decorations
674
+ end
508
675
  visual_lines.each_with_index do |line, index|
676
+ Reline::IOGate.move_cursor_column(0)
509
677
  if line.nil?
510
678
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
511
- # reaches the end of line
512
- if Reline::IOGate.win?
513
- # A newline is automatically inserted if a character is rendered at
514
- # eol on command prompt.
515
- else
516
- # When the cursor is at the end of the line and erases characters
517
- # after the cursor, some terminals delete the character at the
518
- # cursor position.
519
- move_cursor_down(1)
520
- Reline::IOGate.move_cursor_column(0)
521
- end
679
+ # Reaches the end of line.
680
+ #
681
+ # When the cursor is at the end of the line and erases characters
682
+ # after the cursor, some terminals delete the character at the
683
+ # cursor position.
684
+ move_cursor_down(1)
685
+ Reline::IOGate.move_cursor_column(0)
522
686
  else
523
687
  Reline::IOGate.erase_after_cursor
524
688
  move_cursor_down(1)
@@ -527,25 +691,24 @@ class Reline::LineEditor
527
691
  next
528
692
  end
529
693
  @output.write line
530
- if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
531
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
532
- @rest_height -= 1 if @rest_height > 0
533
- end
534
694
  @output.flush
535
695
  if @first_prompt
536
696
  @first_prompt = false
537
697
  @pre_input_hook&.call
538
698
  end
539
699
  end
540
- Reline::IOGate.erase_after_cursor
541
- Reline::IOGate.move_cursor_column(0)
700
+ unless visual_lines.empty?
701
+ Reline::IOGate.erase_after_cursor
702
+ Reline::IOGate.move_cursor_column(0)
703
+ end
542
704
  if with_control
543
705
  # Just after rendring, so the cursor is on the last line.
544
706
  if finished?
545
707
  Reline::IOGate.move_cursor_column(0)
546
708
  else
547
709
  # Moves up from bottom of lines to the cursor position.
548
- move_cursor_up(height - 1 - @started_from)
710
+ move_cursor_up(cursor_up_from_last_line)
711
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
549
712
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
550
713
  end
551
714
  end
@@ -562,6 +725,39 @@ class Reline::LineEditor
562
725
  end
563
726
  end
564
727
 
728
+ private def show_menu
729
+ scroll_down(@highest_in_all - @first_line_started_from)
730
+ @rerender_all = true
731
+ @menu_info.list.sort!.each do |item|
732
+ Reline::IOGate.move_cursor_column(0)
733
+ @output.write item
734
+ @output.flush
735
+ scroll_down(1)
736
+ end
737
+ scroll_down(@highest_in_all - 1)
738
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
739
+ end
740
+
741
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
742
+ Reline::IOGate.clear_screen
743
+ back = 0
744
+ modify_lines(whole_lines).each_with_index do |line, index|
745
+ if @prompt_proc
746
+ pr = prompt_list[index]
747
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
748
+ else
749
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
750
+ end
751
+ if index < (@buffer_of_lines.size - 1)
752
+ move_cursor_down(height)
753
+ back += height
754
+ end
755
+ end
756
+ move_cursor_up(back)
757
+ move_cursor_down(@first_line_started_from + @started_from)
758
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
759
+ end
760
+
565
761
  def editing_mode
566
762
  @config.editing_mode
567
763
  end
@@ -698,6 +894,7 @@ class Reline::LineEditor
698
894
  if @waiting_operator_proc
699
895
  if VI_MOTIONS.include?(method_symbol)
700
896
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
897
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
701
898
  block.(true)
702
899
  unless @waiting_proc
703
900
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
@@ -721,6 +918,8 @@ class Reline::LineEditor
721
918
  block.(false)
722
919
  end
723
920
  @waiting_operator_proc = nil
921
+ @waiting_operator_vi_arg = nil
922
+ @vi_arg = nil
724
923
  else
725
924
  block.(false)
726
925
  end
@@ -846,6 +1045,7 @@ class Reline::LineEditor
846
1045
  end
847
1046
 
848
1047
  def input_key(key)
1048
+ @just_cursor_moving = nil
849
1049
  if key.char.nil?
850
1050
  if @first_char
851
1051
  @line = nil
@@ -853,6 +1053,7 @@ class Reline::LineEditor
853
1053
  finish
854
1054
  return
855
1055
  end
1056
+ old_line = @line.dup
856
1057
  @first_char = false
857
1058
  completion_occurs = false
858
1059
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -881,6 +1082,17 @@ class Reline::LineEditor
881
1082
  unless completion_occurs
882
1083
  @completion_state = CompletionState::NORMAL
883
1084
  end
1085
+ if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1086
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1087
+ @just_cursor_moving = true
1088
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1089
+ @just_cursor_moving = true
1090
+ else
1091
+ @just_cursor_moving = false
1092
+ end
1093
+ else
1094
+ @just_cursor_moving = false
1095
+ end
884
1096
  if @is_multiline and @auto_indent_proc and not simplified_rendering?
885
1097
  process_auto_indent
886
1098
  end
@@ -919,6 +1131,7 @@ class Reline::LineEditor
919
1131
  new_lines = whole_lines
920
1132
  end
921
1133
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1134
+ new_indent = @cursor_max if new_indent&.> @cursor_max
922
1135
  if new_indent&.>= 0
923
1136
  md = new_lines[@line_index].match(/\A */)
924
1137
  prev_indent = md[0].count(' ')
@@ -1112,11 +1325,14 @@ class Reline::LineEditor
1112
1325
 
1113
1326
  private def key_newline(key)
1114
1327
  if @is_multiline
1328
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1329
+ @add_newline_to_end_of_buffer = true
1330
+ end
1115
1331
  next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1116
1332
  cursor_line = @line.byteslice(0, @byte_pointer)
1117
1333
  insert_new_line(cursor_line, next_line)
1118
1334
  @cursor = 0
1119
- @check_new_auto_indent = true
1335
+ @check_new_auto_indent = true unless Reline::IOGate.in_pasting?
1120
1336
  end
1121
1337
  end
1122
1338
 
@@ -1170,7 +1386,12 @@ class Reline::LineEditor
1170
1386
  else
1171
1387
  @line = byteinsert(@line, @byte_pointer, str)
1172
1388
  end
1389
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1173
1390
  @byte_pointer += bytesize
1391
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1392
+ if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1393
+ width = 0
1394
+ end
1174
1395
  @cursor += width
1175
1396
  @cursor_max += width
1176
1397
  end
@@ -1382,9 +1603,11 @@ class Reline::LineEditor
1382
1603
  searcher = generate_searcher
1383
1604
  searcher.resume(key)
1384
1605
  @searching_prompt = "(reverse-i-search)`': "
1606
+ termination_keys = ["\C-j".ord]
1607
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1385
1608
  @waiting_proc = ->(k) {
1386
1609
  case k
1387
- when "\C-j".ord
1610
+ when *termination_keys
1388
1611
  if @history_pointer
1389
1612
  buffer = Reline::HISTORY[@history_pointer]
1390
1613
  else
@@ -1403,6 +1626,8 @@ class Reline::LineEditor
1403
1626
  @waiting_proc = nil
1404
1627
  @cursor_max = calculate_width(@line)
1405
1628
  @cursor = @byte_pointer = 0
1629
+ @rerender_all = true
1630
+ @cached_prompt_list = nil
1406
1631
  searcher.resume(-1)
1407
1632
  when "\C-g".ord
1408
1633
  if @is_multiline
@@ -1446,6 +1671,8 @@ class Reline::LineEditor
1446
1671
  @waiting_proc = nil
1447
1672
  @cursor_max = calculate_width(@line)
1448
1673
  @cursor = @byte_pointer = 0
1674
+ @rerender_all = true
1675
+ @cached_prompt_list = nil
1449
1676
  searcher.resume(-1)
1450
1677
  end
1451
1678
  end
@@ -1498,7 +1725,7 @@ class Reline::LineEditor
1498
1725
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1499
1726
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1500
1727
  @line_index = line_no
1501
- @line = @buffer_of_lines.last
1728
+ @line = @buffer_of_lines[@line_index]
1502
1729
  @rerender_all = true
1503
1730
  else
1504
1731
  @line = Reline::HISTORY[@history_pointer]
@@ -1546,7 +1773,7 @@ class Reline::LineEditor
1546
1773
  @line_index = line_no
1547
1774
  end
1548
1775
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1549
- @line = @buffer_of_lines.last
1776
+ @line = @buffer_of_lines[@line_index]
1550
1777
  @rerender_all = true
1551
1778
  else
1552
1779
  if @history_pointer.nil? and substr.empty?
@@ -1738,6 +1965,7 @@ class Reline::LineEditor
1738
1965
  @cursor = 0
1739
1966
  end
1740
1967
  end
1968
+ alias_method :kill_line, :em_kill_line
1741
1969
 
1742
1970
  private def em_delete(key)
1743
1971
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1788,6 +2016,7 @@ class Reline::LineEditor
1788
2016
  @byte_pointer += yanked.bytesize
1789
2017
  end
1790
2018
  end
2019
+ alias_method :yank, :em_yank
1791
2020
 
1792
2021
  private def em_yank_pop(key)
1793
2022
  yanked, prev_yank = @kill_ring.yank_pop
@@ -1804,6 +2033,7 @@ class Reline::LineEditor
1804
2033
  @byte_pointer += yanked.bytesize
1805
2034
  end
1806
2035
  end
2036
+ alias_method :yank_pop, :em_yank_pop
1807
2037
 
1808
2038
  private def ed_clear_screen(key)
1809
2039
  @cleared = true
@@ -1934,9 +2164,10 @@ class Reline::LineEditor
1934
2164
  @byte_pointer -= byte_size
1935
2165
  @cursor -= width
1936
2166
  @cursor_max -= width
1937
- @kill_ring.append(deleted)
2167
+ @kill_ring.append(deleted, true)
1938
2168
  end
1939
2169
  end
2170
+ alias_method :unix_word_rubout, :em_kill_region
1940
2171
 
1941
2172
  private def copy_for_vi(text)
1942
2173
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@@ -1961,7 +2192,7 @@ class Reline::LineEditor
1961
2192
 
1962
2193
  private def vi_next_word(key, arg: 1)
1963
2194
  if @line.bytesize > @byte_pointer
1964
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2195
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
1965
2196
  @byte_pointer += byte_size
1966
2197
  @cursor += width
1967
2198
  end
@@ -2088,7 +2319,8 @@ class Reline::LineEditor
2088
2319
  @cursor = 0
2089
2320
  end
2090
2321
 
2091
- private def vi_change_meta(key)
2322
+ private def vi_change_meta(key, arg: 1)
2323
+ @drop_terminate_spaces = true
2092
2324
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2093
2325
  if byte_pointer_diff > 0
2094
2326
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2100,10 +2332,12 @@ class Reline::LineEditor
2100
2332
  @cursor_max -= cursor_diff.abs
2101
2333
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2102
2334
  @config.editing_mode = :vi_insert
2335
+ @drop_terminate_spaces = false
2103
2336
  }
2337
+ @waiting_operator_vi_arg = arg
2104
2338
  end
2105
2339
 
2106
- private def vi_delete_meta(key)
2340
+ private def vi_delete_meta(key, arg: 1)
2107
2341
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2108
2342
  if byte_pointer_diff > 0
2109
2343
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2115,9 +2349,10 @@ class Reline::LineEditor
2115
2349
  @cursor_max -= cursor_diff.abs
2116
2350
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2117
2351
  }
2352
+ @waiting_operator_vi_arg = arg
2118
2353
  end
2119
2354
 
2120
- private def vi_yank(key)
2355
+ private def vi_yank(key, arg: 1)
2121
2356
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2122
2357
  if byte_pointer_diff > 0
2123
2358
  cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
@@ -2126,6 +2361,7 @@ class Reline::LineEditor
2126
2361
  end
2127
2362
  copy_for_vi(cut)
2128
2363
  }
2364
+ @waiting_operator_vi_arg = arg
2129
2365
  end
2130
2366
 
2131
2367
  private def vi_list_or_eof(key)
@@ -2152,6 +2388,9 @@ class Reline::LineEditor
2152
2388
  width = Reline::Unicode.get_mbchar_width(mbchar)
2153
2389
  @cursor_max -= width
2154
2390
  if @cursor > 0 and @cursor >= @cursor_max
2391
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2392
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2393
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2155
2394
  @byte_pointer -= byte_size
2156
2395
  @cursor -= width
2157
2396
  end
@@ -2248,7 +2487,7 @@ class Reline::LineEditor
2248
2487
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2249
2488
  before = @line.byteslice(0, @byte_pointer)
2250
2489
  remaining_point = @byte_pointer + byte_size
2251
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2490
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2252
2491
  @line = before + k.chr + after
2253
2492
  @cursor_max = calculate_width(@line)
2254
2493
  @waiting_proc = nil
@@ -2259,7 +2498,7 @@ class Reline::LineEditor
2259
2498
  end
2260
2499
  before = @line.byteslice(0, @byte_pointer)
2261
2500
  remaining_point = @byte_pointer + byte_size
2262
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2501
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2263
2502
  replaced = k.chr * arg
2264
2503
  @line = before + replaced + after
2265
2504
  @byte_pointer += replaced.bytesize