reline 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '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