reline 0.1.6 → 0.2.0

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: '0259fdcb2bf55d2b5532e0da9259247dbee06eefc4564e664006218a094d140f'
4
- data.tar.gz: ea484914d377faed8c7b346fce10a94e996661950714bac68bf8c604a4bd492b
3
+ metadata.gz: 2a20f90b83534916dd8575a842ef2cbe968879cc4b2d1826b5374137f554249a
4
+ data.tar.gz: add41e0e8f89fa39b8064c9d45bc0f17ac1b374578ae25872e24c50cb2e0e761
5
5
  SHA512:
6
- metadata.gz: f108f900ffb86ad334c82f6a64777e78b32e010985f9add33f4ad6ae55f7f1890ed2b16d250d02388ba9b8bc8aed3e985241e3f95b4e43b0d6fe2b9c0ee28057
7
- data.tar.gz: eb45c06febe94e7f953f24db1be2375e619c0fb3d270de7dddc3056c0a473438de2c528e78f6d6682b0a35dcd84d901845aba72c0a16346109b3f84de7276702
6
+ metadata.gz: ebd4286538633a6bb87dd2007bdd6bf389ef87aa5dd7406288ae8f52cacc1e0d7f87c4d3ae4bbac3fa125004d5f65b5740c6b8d2ec014e130160d7bd22c6ec1e
7
+ data.tar.gz: 3a418c2aa202ac4710bb7d90d0017870c066e17ece72c644f90e4d37b9faadb02636150d332c00845c5ca60a421abd1394255f77e95be7626cfce046705b7ed0
@@ -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"
@@ -37,6 +37,8 @@ class Reline::Config
37
37
  vi-cmd-mode-icon
38
38
  vi-ins-mode-icon
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|
@@ -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'
@@ -1,6 +1,10 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
+ def self.reset
5
+ @@pasting = false
6
+ end
7
+
4
8
  def self.encoding
5
9
  RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
6
10
  end
@@ -67,8 +71,18 @@ class Reline::GeneralIO
67
71
  def self.set_winch_handler(&handler)
68
72
  end
69
73
 
74
+ @@pasting = false
75
+
70
76
  def self.in_pasting?
71
- false
77
+ @@pasting
78
+ end
79
+
80
+ def self.start_pasting
81
+ @@pasting = true
82
+ end
83
+
84
+ def self.finish_pasting
85
+ @@pasting = false
72
86
  end
73
87
 
74
88
  def self.prep
@@ -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-^[
@@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
17
17
  # 7 ^G
18
18
  :ed_unassigned,
19
19
  # 8 ^H
20
- :ed_delete_prev_char,
20
+ :ed_unassigned,
21
21
  # 9 ^I
22
22
  :ed_unassigned,
23
23
  # 10 ^J
@@ -255,7 +255,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
255
255
  # 126 ~
256
256
  :vi_change_case,
257
257
  # 127 ^?
258
- :ed_delete_prev_char,
258
+ :ed_unassigned,
259
259
  # 128 M-^@
260
260
  :ed_unassigned,
261
261
  # 129 M-^A
@@ -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_icon
72
+ mode_icon = nil
73
+ if @config.show_mode_in_prompt
74
+ if @config.editing_mode_is?(:vi_command)
75
+ mode_icon = @config.vi_cmd_mode_icon
76
+ elsif @config.editing_mode_is?(:vi_insert)
77
+ mode_icon = @config.vi_ins_mode_icon
78
+ elsif @config.editing_mode_is?(:emacs)
79
+ mode_icon = @config.emacs_mode_string
80
+ else
81
+ mode_icon = '?'
82
+ end
83
+ end
84
+ if mode_icon != @prev_mode_icon
85
+ @rerender_all = true
86
+ end
87
+ @prev_mode_icon = mode_icon
88
+ mode_icon
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,37 @@ 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_icon = check_mode_icon
103
+ prompt = mode_icon + prompt if mode_icon
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
+ if use_cached_prompt_list
116
+ prompt_list = @cached_prompt_list
117
+ else
118
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
119
+ @prompt_cache_time = Time.now.to_f
120
+ end
121
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
122
+ mode_icon = check_mode_icon
123
+ prompt_list = prompt_list.map{ |pr| mode_icon + pr } if mode_icon
93
124
  prompt = prompt_list[@line_index]
125
+ prompt = prompt_list[0] if prompt.nil?
94
126
  prompt_width = calculate_width(prompt, true)
95
127
  [prompt, prompt_width, prompt_list]
96
128
  else
129
+ mode_icon = check_mode_icon
130
+ prompt = mode_icon + prompt if mode_icon
97
131
  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
132
  [prompt, prompt_width, nil]
111
133
  end
112
134
  end
@@ -114,6 +136,7 @@ class Reline::LineEditor
114
136
  def reset(prompt = '', encoding:)
115
137
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
116
138
  @screen_size = Reline::IOGate.get_screen_size
139
+ @screen_height = @screen_size.first
117
140
  reset_variables(prompt, encoding: encoding)
118
141
  @old_trap = Signal.trap('SIGINT') {
119
142
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
@@ -123,6 +146,7 @@ class Reline::LineEditor
123
146
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
124
147
  old_screen_size = @screen_size
125
148
  @screen_size = Reline::IOGate.get_screen_size
149
+ @screen_height = @screen_size.first
126
150
  if old_screen_size.last < @screen_size.last # columns increase
127
151
  @rerender_all = true
128
152
  rerender
@@ -174,11 +198,12 @@ class Reline::LineEditor
174
198
  @cleared = false
175
199
  @rerender_all = false
176
200
  @history_pointer = nil
177
- @kill_ring = Reline::KillRing.new
201
+ @kill_ring ||= Reline::KillRing.new
178
202
  @vi_clipboard = ''
179
203
  @vi_arg = nil
180
204
  @waiting_proc = nil
181
205
  @waiting_operator_proc = nil
206
+ @waiting_operator_vi_arg = nil
182
207
  @completion_journey_data = nil
183
208
  @completion_state = CompletionState::NORMAL
184
209
  @perfect_matched = nil
@@ -186,7 +211,15 @@ class Reline::LineEditor
186
211
  @first_prompt = true
187
212
  @searching_prompt = nil
188
213
  @first_char = true
214
+ @add_newline_to_end_of_buffer = false
215
+ @just_cursor_moving = nil
216
+ @cached_prompt_list = nil
217
+ @prompt_cache_time = nil
189
218
  @eof = false
219
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
220
+ @scroll_partial_screen = nil
221
+ @prev_mode_icon = nil
222
+ @drop_terminate_spaces = false
190
223
  reset_line
191
224
  end
192
225
 
@@ -231,6 +264,7 @@ class Reline::LineEditor
231
264
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
232
265
  @previous_line_index = @line_index
233
266
  @line_index += 1
267
+ @just_cursor_moving = false
234
268
  end
235
269
 
236
270
  private def calculate_height_by_width(width)
@@ -271,28 +305,28 @@ class Reline::LineEditor
271
305
  end
272
306
  end
273
307
 
274
- private def calculate_nearest_cursor
275
- @cursor_max = calculate_width(line)
308
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
309
+ new_cursor_max = calculate_width(line_to_calc)
276
310
  new_cursor = 0
277
311
  new_byte_pointer = 0
278
312
  height = 1
279
313
  max_width = @screen_size.last
280
314
  if @config.editing_mode_is?(:vi_command)
281
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
315
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
282
316
  if last_byte_size > 0
283
- last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
317
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
284
318
  last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
285
- cursor_max = @cursor_max - last_width
319
+ end_of_line_cursor = new_cursor_max - last_width
286
320
  else
287
- cursor_max = @cursor_max
321
+ end_of_line_cursor = new_cursor_max
288
322
  end
289
323
  else
290
- cursor_max = @cursor_max
324
+ end_of_line_cursor = new_cursor_max
291
325
  end
292
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
326
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
293
327
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
294
328
  now = new_cursor + mbchar_width
295
- if now > cursor_max or now > @cursor
329
+ if now > end_of_line_cursor or now > cursor
296
330
  break
297
331
  end
298
332
  new_cursor += mbchar_width
@@ -301,13 +335,20 @@ class Reline::LineEditor
301
335
  end
302
336
  new_byte_pointer += gc.bytesize
303
337
  end
304
- @started_from = height - 1
305
- @cursor = new_cursor
306
- @byte_pointer = new_byte_pointer
338
+ new_started_from = height - 1
339
+ if update
340
+ @cursor = new_cursor
341
+ @cursor_max = new_cursor_max
342
+ @started_from = new_started_from
343
+ @byte_pointer = new_byte_pointer
344
+ else
345
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
346
+ end
307
347
  end
308
348
 
309
349
  def rerender_all
310
350
  @rerender_all = true
351
+ process_insert(force: true)
311
352
  rerender
312
353
  end
313
354
 
@@ -316,168 +357,53 @@ class Reline::LineEditor
316
357
  if @menu_info
317
358
  scroll_down(@highest_in_all - @first_line_started_from)
318
359
  @rerender_all = true
319
- @menu_info.list.sort!.each do |item|
320
- Reline::IOGate.move_cursor_column(0)
321
- @output.write item
322
- @output.flush
323
- scroll_down(1)
324
- end
325
- scroll_down(@highest_in_all - 1)
326
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
360
+ end
361
+ if @menu_info
362
+ show_menu
327
363
  @menu_info = nil
328
364
  end
329
365
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
330
366
  if @cleared
331
- Reline::IOGate.clear_screen
367
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
332
368
  @cleared = false
333
- back = 0
334
- modify_lines(whole_lines).each_with_index do |line, index|
335
- if @prompt_proc
336
- pr = prompt_list[index]
337
- height = render_partial(pr, calculate_width(pr), line, false)
338
- else
339
- height = render_partial(prompt, prompt_width, line, false)
340
- end
341
- if index < (@buffer_of_lines.size - 1)
342
- move_cursor_down(height)
343
- back += height
344
- end
345
- end
346
- move_cursor_up(back)
347
- move_cursor_down(@first_line_started_from + @started_from)
348
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
349
369
  return
350
370
  end
351
371
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
352
372
  # FIXME: end of logical line sometimes breaks
353
- if @previous_line_index or new_highest_in_this != @highest_in_this
354
- if @previous_line_index
355
- new_lines = whole_lines(index: @previous_line_index, line: @line)
356
- else
357
- new_lines = whole_lines
358
- end
359
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
360
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
361
- diff = all_height - @highest_in_all
362
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
363
- if diff > 0
364
- scroll_down(diff)
365
- move_cursor_up(all_height - 1)
366
- elsif diff < 0
367
- (-diff).times do
368
- Reline::IOGate.move_cursor_column(0)
369
- Reline::IOGate.erase_after_cursor
370
- move_cursor_up(1)
371
- end
372
- move_cursor_up(all_height - 1)
373
+ if @add_newline_to_end_of_buffer
374
+ rerender_added_newline
375
+ @add_newline_to_end_of_buffer = false
376
+ else
377
+ if @just_cursor_moving and not @rerender_all
378
+ rendered = just_move_cursor
379
+ @just_cursor_moving = false
380
+ return
381
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
382
+ rerender_changed_current_line
383
+ @previous_line_index = nil
384
+ rendered = true
385
+ elsif @rerender_all
386
+ rerender_all_lines
387
+ @rerender_all = false
388
+ rendered = true
373
389
  else
374
- move_cursor_up(all_height - 1)
375
- end
376
- @highest_in_all = all_height
377
- back = 0
378
- modify_lines(new_lines).each_with_index do |line, index|
379
- if @prompt_proc
380
- prompt = prompt_list[index]
381
- prompt_width = calculate_width(prompt, true)
382
- end
383
- height = render_partial(prompt, prompt_width, line, false)
384
- if index < (new_lines.size - 1)
385
- scroll_down(1)
386
- back += height
387
- else
388
- back += height - 1
389
- end
390
390
  end
391
- move_cursor_up(back)
392
- if @previous_line_index
393
- @buffer_of_lines[@previous_line_index] = @line
394
- @line = @buffer_of_lines[@line_index]
395
- end
396
- @first_line_started_from =
397
- if @line_index.zero?
398
- 0
399
- else
400
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
401
- end
402
- if @prompt_proc
403
- prompt = prompt_list[@line_index]
404
- prompt_width = calculate_width(prompt, true)
405
- end
406
- move_cursor_down(@first_line_started_from)
407
- calculate_nearest_cursor
408
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
409
- move_cursor_down(@started_from)
410
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
411
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
412
- @previous_line_index = nil
413
- rendered = true
414
- elsif @rerender_all
415
- move_cursor_up(@first_line_started_from + @started_from)
416
- Reline::IOGate.move_cursor_column(0)
417
- back = 0
418
- new_buffer = whole_lines
419
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
420
- new_buffer.each_with_index do |line, index|
421
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
422
- width = prompt_width + calculate_width(line)
423
- height = calculate_height_by_width(width)
424
- back += height
425
- end
426
- if back > @highest_in_all
427
- scroll_down(back - 1)
428
- move_cursor_up(back - 1)
429
- elsif back < @highest_in_all
430
- scroll_down(back)
431
- Reline::IOGate.erase_after_cursor
432
- (@highest_in_all - back - 1).times do
433
- scroll_down(1)
434
- Reline::IOGate.erase_after_cursor
435
- end
436
- move_cursor_up(@highest_in_all - 1)
437
- end
438
- modify_lines(new_buffer).each_with_index do |line, index|
439
- if @prompt_proc
440
- prompt = prompt_list[index]
441
- prompt_width = calculate_width(prompt, true)
442
- end
443
- render_partial(prompt, prompt_width, line, false)
444
- if index < (new_buffer.size - 1)
445
- move_cursor_down(1)
446
- end
447
- end
448
- move_cursor_up(back - 1)
449
- if @prompt_proc
450
- prompt = prompt_list[@line_index]
451
- prompt_width = calculate_width(prompt, true)
452
- end
453
- @highest_in_all = back
454
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
455
- @first_line_started_from =
456
- if @line_index.zero?
457
- 0
458
- else
459
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
460
- end
461
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
462
- move_cursor_down(@first_line_started_from + @started_from)
463
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
464
- @rerender_all = false
465
- rendered = true
466
391
  end
467
392
  line = modify_lines(whole_lines)[@line_index]
468
393
  if @is_multiline
469
394
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
470
395
  if finished?
471
396
  # Always rerender on finish because output_modifier_proc may return a different output.
472
- render_partial(prompt, prompt_width, line)
397
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
473
398
  scroll_down(1)
474
399
  Reline::IOGate.move_cursor_column(0)
475
400
  Reline::IOGate.erase_after_cursor
476
401
  elsif not rendered
477
- render_partial(prompt, prompt_width, line)
402
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
478
403
  end
404
+ @buffer_of_lines[@line_index] = @line
479
405
  else
480
- render_partial(prompt, prompt_width, line)
406
+ render_partial(prompt, prompt_width, line, 0)
481
407
  if finished?
482
408
  scroll_down(1)
483
409
  Reline::IOGate.move_cursor_column(0)
@@ -486,8 +412,237 @@ class Reline::LineEditor
486
412
  end
487
413
  end
488
414
 
489
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
415
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
416
+ if @screen_height < highest_in_all
417
+ old_scroll_partial_screen = @scroll_partial_screen
418
+ if cursor_y == 0
419
+ @scroll_partial_screen = 0
420
+ elsif cursor_y == (highest_in_all - 1)
421
+ @scroll_partial_screen = highest_in_all - @screen_height
422
+ else
423
+ if @scroll_partial_screen
424
+ if cursor_y <= @scroll_partial_screen
425
+ @scroll_partial_screen = cursor_y
426
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
427
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
428
+ end
429
+ else
430
+ if cursor_y > (@screen_height - 1)
431
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
432
+ else
433
+ @scroll_partial_screen = 0
434
+ end
435
+ end
436
+ end
437
+ if @scroll_partial_screen != old_scroll_partial_screen
438
+ @rerender_all = true
439
+ end
440
+ else
441
+ if @scroll_partial_screen
442
+ @rerender_all = true
443
+ end
444
+ @scroll_partial_screen = nil
445
+ end
446
+ end
447
+
448
+ private def rerender_added_newline
449
+ scroll_down(1)
450
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
451
+ prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
452
+ @buffer_of_lines[@previous_line_index] = @line
453
+ @line = @buffer_of_lines[@line_index]
454
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
455
+ @cursor = @cursor_max = calculate_width(@line)
456
+ @byte_pointer = @line.bytesize
457
+ @highest_in_all += @highest_in_this
458
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
459
+ @first_line_started_from += @started_from + 1
460
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
461
+ @previous_line_index = nil
462
+ end
463
+
464
+ def just_move_cursor
465
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
466
+ move_cursor_up(@started_from)
467
+ new_first_line_started_from =
468
+ if @line_index.zero?
469
+ 0
470
+ else
471
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
472
+ end
473
+ first_line_diff = new_first_line_started_from - @first_line_started_from
474
+ new_cursor, _, new_started_from, _ = calculate_nearest_cursor(@line, @cursor, @started_from, @byte_pointer, false)
475
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
476
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
477
+ @previous_line_index = nil
478
+ if @rerender_all
479
+ @line = @buffer_of_lines[@line_index]
480
+ rerender_all_lines
481
+ @rerender_all = false
482
+ true
483
+ else
484
+ @line = @buffer_of_lines[@line_index]
485
+ @first_line_started_from = new_first_line_started_from
486
+ @started_from = new_started_from
487
+ @cursor = new_cursor
488
+ move_cursor_down(first_line_diff + @started_from)
489
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
490
+ false
491
+ end
492
+ end
493
+
494
+ private def rerender_changed_current_line
495
+ if @previous_line_index
496
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
497
+ else
498
+ new_lines = whole_lines
499
+ end
500
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
501
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
502
+ diff = all_height - @highest_in_all
503
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
504
+ if diff > 0
505
+ scroll_down(diff)
506
+ move_cursor_up(all_height - 1)
507
+ elsif diff < 0
508
+ (-diff).times do
509
+ Reline::IOGate.move_cursor_column(0)
510
+ Reline::IOGate.erase_after_cursor
511
+ move_cursor_up(1)
512
+ end
513
+ move_cursor_up(all_height - 1)
514
+ else
515
+ move_cursor_up(all_height - 1)
516
+ end
517
+ @highest_in_all = all_height
518
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
519
+ move_cursor_up(back)
520
+ if @previous_line_index
521
+ @buffer_of_lines[@previous_line_index] = @line
522
+ @line = @buffer_of_lines[@line_index]
523
+ end
524
+ @first_line_started_from =
525
+ if @line_index.zero?
526
+ 0
527
+ else
528
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
529
+ end
530
+ if @prompt_proc
531
+ prompt = prompt_list[@line_index]
532
+ prompt_width = calculate_width(prompt, true)
533
+ end
534
+ move_cursor_down(@first_line_started_from)
535
+ calculate_nearest_cursor
536
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
537
+ move_cursor_down(@started_from)
538
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
539
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
540
+ end
541
+
542
+ private def rerender_all_lines
543
+ move_cursor_up(@first_line_started_from + @started_from)
544
+ Reline::IOGate.move_cursor_column(0)
545
+ back = 0
546
+ new_buffer = whole_lines
547
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
548
+ new_buffer.each_with_index do |line, index|
549
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
550
+ width = prompt_width + calculate_width(line)
551
+ height = calculate_height_by_width(width)
552
+ back += height
553
+ end
554
+ old_highest_in_all = @highest_in_all
555
+ if @line_index.zero?
556
+ new_first_line_started_from = 0
557
+ else
558
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
559
+ end
560
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
561
+ if back > old_highest_in_all
562
+ scroll_down(back - 1)
563
+ move_cursor_up(back - 1)
564
+ elsif back < old_highest_in_all
565
+ scroll_down(back)
566
+ Reline::IOGate.erase_after_cursor
567
+ (old_highest_in_all - back - 1).times do
568
+ scroll_down(1)
569
+ Reline::IOGate.erase_after_cursor
570
+ end
571
+ move_cursor_up(old_highest_in_all - 1)
572
+ end
573
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
574
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
575
+ if @prompt_proc
576
+ prompt = prompt_list[@line_index]
577
+ prompt_width = calculate_width(prompt, true)
578
+ end
579
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
580
+ @highest_in_all = back
581
+ @first_line_started_from = new_first_line_started_from
582
+ @started_from = new_started_from
583
+ if @scroll_partial_screen
584
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
585
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
586
+ else
587
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
588
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
589
+ end
590
+ end
591
+
592
+ private def render_whole_lines(lines, prompt, prompt_width)
593
+ rendered_height = 0
594
+ modify_lines(lines).each_with_index do |line, index|
595
+ if prompt.is_a?(Array)
596
+ line_prompt = prompt[index]
597
+ prompt_width = calculate_width(line_prompt, true)
598
+ else
599
+ line_prompt = prompt
600
+ end
601
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
602
+ if index < (lines.size - 1)
603
+ if @scroll_partial_screen
604
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
605
+ move_cursor_down(1)
606
+ end
607
+ else
608
+ scroll_down(1)
609
+ end
610
+ rendered_height += height
611
+ else
612
+ rendered_height += height - 1
613
+ end
614
+ end
615
+ rendered_height
616
+ end
617
+
618
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
490
619
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
620
+ cursor_up_from_last_line = 0
621
+ # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
622
+ if @scroll_partial_screen
623
+ last_visual_line = this_started_from + (height - 1)
624
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
625
+ if (@scroll_partial_screen - this_started_from) >= height
626
+ # Render nothing because this line is before the screen.
627
+ visual_lines = []
628
+ elsif this_started_from > last_screen_line
629
+ # Render nothing because this line is after the screen.
630
+ visual_lines = []
631
+ else
632
+ deleted_lines_before_screen = []
633
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
634
+ # A part of visual lines are before the screen.
635
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
636
+ deleted_lines_before_screen.compact!
637
+ end
638
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
639
+ # A part of visual lines are after the screen.
640
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
641
+ end
642
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
643
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
644
+ end
645
+ end
491
646
  if with_control
492
647
  if height > @highest_in_this
493
648
  diff = height - @highest_in_this
@@ -501,10 +656,14 @@ class Reline::LineEditor
501
656
  @highest_in_this = height
502
657
  end
503
658
  move_cursor_up(@started_from)
659
+ cursor_up_from_last_line = height - 1 - @started_from
504
660
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
505
661
  end
506
- Reline::IOGate.move_cursor_column(0)
662
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
663
+ @output.write "\e[0m" # clear character decorations
664
+ end
507
665
  visual_lines.each_with_index do |line, index|
666
+ Reline::IOGate.move_cursor_column(0)
508
667
  if line.nil?
509
668
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
510
669
  # reaches the end of line
@@ -536,15 +695,18 @@ class Reline::LineEditor
536
695
  @pre_input_hook&.call
537
696
  end
538
697
  end
539
- Reline::IOGate.erase_after_cursor
540
- Reline::IOGate.move_cursor_column(0)
698
+ unless visual_lines.empty?
699
+ Reline::IOGate.erase_after_cursor
700
+ Reline::IOGate.move_cursor_column(0)
701
+ end
541
702
  if with_control
542
703
  # Just after rendring, so the cursor is on the last line.
543
704
  if finished?
544
705
  Reline::IOGate.move_cursor_column(0)
545
706
  else
546
707
  # Moves up from bottom of lines to the cursor position.
547
- move_cursor_up(height - 1 - @started_from)
708
+ move_cursor_up(cursor_up_from_last_line)
709
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
548
710
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
549
711
  end
550
712
  end
@@ -561,6 +723,39 @@ class Reline::LineEditor
561
723
  end
562
724
  end
563
725
 
726
+ private def show_menu
727
+ scroll_down(@highest_in_all - @first_line_started_from)
728
+ @rerender_all = true
729
+ @menu_info.list.sort!.each do |item|
730
+ Reline::IOGate.move_cursor_column(0)
731
+ @output.write item
732
+ @output.flush
733
+ scroll_down(1)
734
+ end
735
+ scroll_down(@highest_in_all - 1)
736
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
737
+ end
738
+
739
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
740
+ Reline::IOGate.clear_screen
741
+ back = 0
742
+ modify_lines(whole_lines).each_with_index do |line, index|
743
+ if @prompt_proc
744
+ pr = prompt_list[index]
745
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
746
+ else
747
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
748
+ end
749
+ if index < (@buffer_of_lines.size - 1)
750
+ move_cursor_down(height)
751
+ back += height
752
+ end
753
+ end
754
+ move_cursor_up(back)
755
+ move_cursor_down(@first_line_started_from + @started_from)
756
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
757
+ end
758
+
564
759
  def editing_mode
565
760
  @config.editing_mode
566
761
  end
@@ -697,7 +892,8 @@ class Reline::LineEditor
697
892
  if @waiting_operator_proc
698
893
  if VI_MOTIONS.include?(method_symbol)
699
894
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
700
- block.()
895
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
896
+ block.(true)
701
897
  unless @waiting_proc
702
898
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
703
899
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
@@ -705,27 +901,56 @@ class Reline::LineEditor
705
901
  else
706
902
  old_waiting_proc = @waiting_proc
707
903
  old_waiting_operator_proc = @waiting_operator_proc
904
+ current_waiting_operator_proc = @waiting_operator_proc
708
905
  @waiting_proc = proc { |k|
709
906
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
710
907
  old_waiting_proc.(k)
711
908
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
712
909
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
713
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
910
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
714
911
  @waiting_operator_proc = old_waiting_operator_proc
715
912
  }
716
913
  end
717
914
  else
718
915
  # Ignores operator when not motion is given.
719
- block.()
916
+ block.(false)
720
917
  end
721
918
  @waiting_operator_proc = nil
919
+ @waiting_operator_vi_arg = nil
920
+ @vi_arg = nil
722
921
  else
723
- block.()
922
+ block.(false)
724
923
  end
725
924
  end
726
925
 
727
926
  private def argumentable?(method_obj)
728
- method_obj and method_obj.parameters.length != 1
927
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
928
+ end
929
+
930
+ private def inclusive?(method_obj)
931
+ # If a motion method with the keyword argument "inclusive" follows the
932
+ # operator, it must contain the character at the cursor position.
933
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
934
+ end
935
+
936
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
937
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
938
+ not_insertion = method_symbol != :ed_insert
939
+ process_insert(force: not_insertion)
940
+ end
941
+ if @vi_arg and argumentable?(method_obj)
942
+ if with_operator and inclusive?(method_obj)
943
+ method_obj.(key, arg: @vi_arg, inclusive: true)
944
+ else
945
+ method_obj.(key, arg: @vi_arg)
946
+ end
947
+ else
948
+ if with_operator and inclusive?(method_obj)
949
+ method_obj.(key, inclusive: true)
950
+ else
951
+ method_obj.(key)
952
+ end
953
+ end
729
954
  end
730
955
 
731
956
  private def process_key(key, method_symbol)
@@ -736,11 +961,11 @@ class Reline::LineEditor
736
961
  end
737
962
  if method_symbol and key.is_a?(Symbol)
738
963
  if @vi_arg and argumentable?(method_obj)
739
- run_for_operators(key, method_symbol) do
740
- method_obj.(key, arg: @vi_arg)
964
+ run_for_operators(key, method_symbol) do |with_operator|
965
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
741
966
  end
742
967
  else
743
- method_obj&.(key)
968
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
744
969
  end
745
970
  @kill_ring.process
746
971
  @vi_arg = nil
@@ -749,15 +974,15 @@ class Reline::LineEditor
749
974
  ed_argument_digit(key)
750
975
  else
751
976
  if argumentable?(method_obj)
752
- run_for_operators(key, method_symbol) do
753
- method_obj.(key, arg: @vi_arg)
977
+ run_for_operators(key, method_symbol) do |with_operator|
978
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
754
979
  end
755
980
  elsif @waiting_proc
756
981
  @waiting_proc.(key)
757
982
  elsif method_obj
758
- method_obj.(key)
983
+ wrap_method_call(method_symbol, method_obj, key)
759
984
  else
760
- ed_insert(key)
985
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
761
986
  end
762
987
  @kill_ring.process
763
988
  @vi_arg = nil
@@ -767,15 +992,15 @@ class Reline::LineEditor
767
992
  @kill_ring.process
768
993
  elsif method_obj
769
994
  if method_symbol == :ed_argument_digit
770
- method_obj.(key)
995
+ wrap_method_call(method_symbol, method_obj, key)
771
996
  else
772
- run_for_operators(key, method_symbol) do
773
- method_obj.(key)
997
+ run_for_operators(key, method_symbol) do |with_operator|
998
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
774
999
  end
775
1000
  end
776
1001
  @kill_ring.process
777
1002
  else
778
- ed_insert(key)
1003
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
779
1004
  end
780
1005
  end
781
1006
 
@@ -818,6 +1043,7 @@ class Reline::LineEditor
818
1043
  end
819
1044
 
820
1045
  def input_key(key)
1046
+ @just_cursor_moving = nil
821
1047
  if key.char.nil?
822
1048
  if @first_char
823
1049
  @line = nil
@@ -825,6 +1051,7 @@ class Reline::LineEditor
825
1051
  finish
826
1052
  return
827
1053
  end
1054
+ old_line = @line.dup
828
1055
  @first_char = false
829
1056
  completion_occurs = false
830
1057
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -832,6 +1059,7 @@ class Reline::LineEditor
832
1059
  result = call_completion_proc
833
1060
  if result.is_a?(Array)
834
1061
  completion_occurs = true
1062
+ process_insert
835
1063
  complete(result)
836
1064
  end
837
1065
  end
@@ -840,6 +1068,7 @@ class Reline::LineEditor
840
1068
  result = call_completion_proc
841
1069
  if result.is_a?(Array)
842
1070
  completion_occurs = true
1071
+ process_insert
843
1072
  move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
844
1073
  end
845
1074
  end
@@ -851,6 +1080,17 @@ class Reline::LineEditor
851
1080
  unless completion_occurs
852
1081
  @completion_state = CompletionState::NORMAL
853
1082
  end
1083
+ if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1084
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1085
+ @just_cursor_moving = true
1086
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1087
+ @just_cursor_moving = true
1088
+ else
1089
+ @just_cursor_moving = false
1090
+ end
1091
+ else
1092
+ @just_cursor_moving = false
1093
+ end
854
1094
  if @is_multiline and @auto_indent_proc and not simplified_rendering?
855
1095
  process_auto_indent
856
1096
  end
@@ -1082,6 +1322,9 @@ class Reline::LineEditor
1082
1322
 
1083
1323
  private def key_newline(key)
1084
1324
  if @is_multiline
1325
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1326
+ @add_newline_to_end_of_buffer = true
1327
+ end
1085
1328
  next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1086
1329
  cursor_line = @line.byteslice(0, @byte_pointer)
1087
1330
  insert_new_line(cursor_line, next_line)
@@ -1092,38 +1335,62 @@ class Reline::LineEditor
1092
1335
 
1093
1336
  private def ed_unassigned(key) end # do nothing
1094
1337
 
1338
+ private def process_insert(force: false)
1339
+ return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1340
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1341
+ bytesize = @continuous_insertion_buffer.bytesize
1342
+ if @cursor == @cursor_max
1343
+ @line += @continuous_insertion_buffer
1344
+ else
1345
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1346
+ end
1347
+ @byte_pointer += bytesize
1348
+ @cursor += width
1349
+ @cursor_max += width
1350
+ @continuous_insertion_buffer.clear
1351
+ end
1352
+
1095
1353
  private def ed_insert(key)
1354
+ str = nil
1355
+ width = nil
1356
+ bytesize = nil
1096
1357
  if key.instance_of?(String)
1097
1358
  begin
1098
1359
  key.encode(Encoding::UTF_8)
1099
1360
  rescue Encoding::UndefinedConversionError
1100
1361
  return
1101
1362
  end
1102
- width = Reline::Unicode.get_mbchar_width(key)
1103
- if @cursor == @cursor_max
1104
- @line += key
1105
- else
1106
- @line = byteinsert(@line, @byte_pointer, key)
1107
- end
1108
- @byte_pointer += key.bytesize
1109
- @cursor += width
1110
- @cursor_max += width
1363
+ str = key
1364
+ bytesize = key.bytesize
1111
1365
  else
1112
1366
  begin
1113
1367
  key.chr.encode(Encoding::UTF_8)
1114
1368
  rescue Encoding::UndefinedConversionError
1115
1369
  return
1116
1370
  end
1117
- if @cursor == @cursor_max
1118
- @line += key.chr
1119
- else
1120
- @line = byteinsert(@line, @byte_pointer, key.chr)
1121
- end
1122
- width = Reline::Unicode.get_mbchar_width(key.chr)
1123
- @byte_pointer += 1
1124
- @cursor += width
1125
- @cursor_max += width
1371
+ str = key.chr
1372
+ bytesize = 1
1373
+ end
1374
+ if Reline::IOGate.in_pasting?
1375
+ @continuous_insertion_buffer << str
1376
+ return
1377
+ elsif not @continuous_insertion_buffer.empty?
1378
+ process_insert
1379
+ end
1380
+ width = Reline::Unicode.get_mbchar_width(str)
1381
+ if @cursor == @cursor_max
1382
+ @line += str
1383
+ else
1384
+ @line = byteinsert(@line, @byte_pointer, str)
1126
1385
  end
1386
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1387
+ @byte_pointer += bytesize
1388
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1389
+ if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1390
+ width = 0
1391
+ end
1392
+ @cursor += width
1393
+ @cursor_max += width
1127
1394
  end
1128
1395
  alias_method :ed_digit, :ed_insert
1129
1396
  alias_method :self_insert, :ed_insert
@@ -1180,6 +1447,7 @@ class Reline::LineEditor
1180
1447
  arg -= 1
1181
1448
  ed_prev_char(key, arg: arg) if arg > 0
1182
1449
  end
1450
+ alias_method :backward_char, :ed_prev_char
1183
1451
 
1184
1452
  private def vi_first_print(key)
1185
1453
  @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
@@ -1332,9 +1600,11 @@ class Reline::LineEditor
1332
1600
  searcher = generate_searcher
1333
1601
  searcher.resume(key)
1334
1602
  @searching_prompt = "(reverse-i-search)`': "
1603
+ termination_keys = ["\C-j".ord]
1604
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1335
1605
  @waiting_proc = ->(k) {
1336
1606
  case k
1337
- when "\C-j".ord
1607
+ when *termination_keys
1338
1608
  if @history_pointer
1339
1609
  buffer = Reline::HISTORY[@history_pointer]
1340
1610
  else
@@ -1353,6 +1623,8 @@ class Reline::LineEditor
1353
1623
  @waiting_proc = nil
1354
1624
  @cursor_max = calculate_width(@line)
1355
1625
  @cursor = @byte_pointer = 0
1626
+ @rerender_all = true
1627
+ @cached_prompt_list = nil
1356
1628
  searcher.resume(-1)
1357
1629
  when "\C-g".ord
1358
1630
  if @is_multiline
@@ -1396,6 +1668,8 @@ class Reline::LineEditor
1396
1668
  @waiting_proc = nil
1397
1669
  @cursor_max = calculate_width(@line)
1398
1670
  @cursor = @byte_pointer = 0
1671
+ @rerender_all = true
1672
+ @cached_prompt_list = nil
1399
1673
  searcher.resume(-1)
1400
1674
  end
1401
1675
  end
@@ -1609,6 +1883,7 @@ class Reline::LineEditor
1609
1883
  end
1610
1884
 
1611
1885
  private def ed_newline(key)
1886
+ process_insert(force: true)
1612
1887
  if @is_multiline
1613
1888
  if @config.editing_mode_is?(:vi_command)
1614
1889
  if @line_index < (@buffer_of_lines.size - 1)
@@ -1687,6 +1962,7 @@ class Reline::LineEditor
1687
1962
  @cursor = 0
1688
1963
  end
1689
1964
  end
1965
+ alias_method :kill_line, :em_kill_line
1690
1966
 
1691
1967
  private def em_delete(key)
1692
1968
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1737,6 +2013,7 @@ class Reline::LineEditor
1737
2013
  @byte_pointer += yanked.bytesize
1738
2014
  end
1739
2015
  end
2016
+ alias_method :yank, :em_yank
1740
2017
 
1741
2018
  private def em_yank_pop(key)
1742
2019
  yanked, prev_yank = @kill_ring.yank_pop
@@ -1753,6 +2030,7 @@ class Reline::LineEditor
1753
2030
  @byte_pointer += yanked.bytesize
1754
2031
  end
1755
2032
  end
2033
+ alias_method :yank_pop, :em_yank_pop
1756
2034
 
1757
2035
  private def ed_clear_screen(key)
1758
2036
  @cleared = true
@@ -1883,9 +2161,10 @@ class Reline::LineEditor
1883
2161
  @byte_pointer -= byte_size
1884
2162
  @cursor -= width
1885
2163
  @cursor_max -= width
1886
- @kill_ring.append(deleted)
2164
+ @kill_ring.append(deleted, true)
1887
2165
  end
1888
2166
  end
2167
+ alias_method :unix_word_rubout, :em_kill_region
1889
2168
 
1890
2169
  private def copy_for_vi(text)
1891
2170
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@@ -1906,11 +2185,11 @@ class Reline::LineEditor
1906
2185
  ed_prev_char(key)
1907
2186
  @config.editing_mode = :vi_command
1908
2187
  end
1909
- alias_method :backward_char, :ed_prev_char
2188
+ alias_method :vi_movement_mode, :vi_command_mode
1910
2189
 
1911
2190
  private def vi_next_word(key, arg: 1)
1912
2191
  if @line.bytesize > @byte_pointer
1913
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2192
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
1914
2193
  @byte_pointer += byte_size
1915
2194
  @cursor += width
1916
2195
  end
@@ -1928,13 +2207,22 @@ class Reline::LineEditor
1928
2207
  vi_prev_word(key, arg: arg) if arg > 0
1929
2208
  end
1930
2209
 
1931
- private def vi_end_word(key, arg: 1)
2210
+ private def vi_end_word(key, arg: 1, inclusive: false)
1932
2211
  if @line.bytesize > @byte_pointer
1933
2212
  byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1934
2213
  @byte_pointer += byte_size
1935
2214
  @cursor += width
1936
2215
  end
1937
2216
  arg -= 1
2217
+ if inclusive and arg.zero?
2218
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2219
+ if byte_size > 0
2220
+ c = @line.byteslice(@byte_pointer, byte_size)
2221
+ width = Reline::Unicode.get_mbchar_width(c)
2222
+ @byte_pointer += byte_size
2223
+ @cursor += width
2224
+ end
2225
+ end
1938
2226
  vi_end_word(key, arg: arg) if arg > 0
1939
2227
  end
1940
2228
 
@@ -1958,13 +2246,22 @@ class Reline::LineEditor
1958
2246
  vi_prev_big_word(key, arg: arg) if arg > 0
1959
2247
  end
1960
2248
 
1961
- private def vi_end_big_word(key, arg: 1)
2249
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1962
2250
  if @line.bytesize > @byte_pointer
1963
2251
  byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1964
2252
  @byte_pointer += byte_size
1965
2253
  @cursor += width
1966
2254
  end
1967
2255
  arg -= 1
2256
+ if inclusive and arg.zero?
2257
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2258
+ if byte_size > 0
2259
+ c = @line.byteslice(@byte_pointer, byte_size)
2260
+ width = Reline::Unicode.get_mbchar_width(c)
2261
+ @byte_pointer += byte_size
2262
+ @cursor += width
2263
+ end
2264
+ end
1968
2265
  vi_end_big_word(key, arg: arg) if arg > 0
1969
2266
  end
1970
2267
 
@@ -2019,7 +2316,8 @@ class Reline::LineEditor
2019
2316
  @cursor = 0
2020
2317
  end
2021
2318
 
2022
- private def vi_change_meta(key)
2319
+ private def vi_change_meta(key, arg: 1)
2320
+ @drop_terminate_spaces = true
2023
2321
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2024
2322
  if byte_pointer_diff > 0
2025
2323
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2031,10 +2329,12 @@ class Reline::LineEditor
2031
2329
  @cursor_max -= cursor_diff.abs
2032
2330
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2033
2331
  @config.editing_mode = :vi_insert
2332
+ @drop_terminate_spaces = false
2034
2333
  }
2334
+ @waiting_operator_vi_arg = arg
2035
2335
  end
2036
2336
 
2037
- private def vi_delete_meta(key)
2337
+ private def vi_delete_meta(key, arg: 1)
2038
2338
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2039
2339
  if byte_pointer_diff > 0
2040
2340
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2046,9 +2346,19 @@ class Reline::LineEditor
2046
2346
  @cursor_max -= cursor_diff.abs
2047
2347
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2048
2348
  }
2349
+ @waiting_operator_vi_arg = arg
2049
2350
  end
2050
2351
 
2051
- private def vi_yank(key)
2352
+ private def vi_yank(key, arg: 1)
2353
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2354
+ if byte_pointer_diff > 0
2355
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2356
+ elsif byte_pointer_diff < 0
2357
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2358
+ end
2359
+ copy_for_vi(cut)
2360
+ }
2361
+ @waiting_operator_vi_arg = arg
2052
2362
  end
2053
2363
 
2054
2364
  private def vi_list_or_eof(key)
@@ -2171,7 +2481,7 @@ class Reline::LineEditor
2171
2481
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2172
2482
  before = @line.byteslice(0, @byte_pointer)
2173
2483
  remaining_point = @byte_pointer + byte_size
2174
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2484
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2175
2485
  @line = before + k.chr + after
2176
2486
  @cursor_max = calculate_width(@line)
2177
2487
  @waiting_proc = nil
@@ -2182,7 +2492,7 @@ class Reline::LineEditor
2182
2492
  end
2183
2493
  before = @line.byteslice(0, @byte_pointer)
2184
2494
  remaining_point = @byte_pointer + byte_size
2185
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2495
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2186
2496
  replaced = k.chr * arg
2187
2497
  @line = before + replaced + after
2188
2498
  @byte_pointer += replaced.bytesize
@@ -2193,15 +2503,15 @@ class Reline::LineEditor
2193
2503
  }
2194
2504
  end
2195
2505
 
2196
- private def vi_next_char(key, arg: 1)
2197
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
2506
+ private def vi_next_char(key, arg: 1, inclusive: false)
2507
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2198
2508
  end
2199
2509
 
2200
- private def vi_to_next_char(key, arg: 1)
2201
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
2510
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2511
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2202
2512
  end
2203
2513
 
2204
- private def search_next_char(key, arg, need_prev_char = false)
2514
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2205
2515
  if key.instance_of?(String)
2206
2516
  inputed_char = key
2207
2517
  else
@@ -2238,6 +2548,15 @@ class Reline::LineEditor
2238
2548
  @byte_pointer += byte_size
2239
2549
  @cursor += width
2240
2550
  end
2551
+ if inclusive
2552
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2553
+ if byte_size > 0
2554
+ c = @line.byteslice(@byte_pointer, byte_size)
2555
+ width = Reline::Unicode.get_mbchar_width(c)
2556
+ @byte_pointer += byte_size
2557
+ @cursor += width
2558
+ end
2559
+ end
2241
2560
  @waiting_proc = nil
2242
2561
  end
2243
2562
 
@@ -2309,6 +2628,7 @@ class Reline::LineEditor
2309
2628
  alias_method :set_mark, :em_set_mark
2310
2629
 
2311
2630
  private def em_exchange_mark(key)
2631
+ return unless @mark_pointer
2312
2632
  new_pointer = [@byte_pointer, @line_index]
2313
2633
  @previous_line_index = @line_index
2314
2634
  @byte_pointer, @line_index = @mark_pointer