reline 0.1.7 → 0.2.1

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: bba0b20f31c4777e7cdce2647964cc5837a1646a000019e0b376da8327b0babe
4
- data.tar.gz: 887ac3397cb9f1fcfaf60c61fc90ec31c397fc01516f343fff34bf813b2d8f7f
3
+ metadata.gz: 9ad1ae315ec2e7717822c6dd2cde886639d5becb7ccf552a64921e12b8452107
4
+ data.tar.gz: 191aa399008fb54a8ee3048f5ef96b260d1937fba8203977e15321f45a3fd962
5
5
  SHA512:
6
- metadata.gz: 5361850fc7402585b7bf9590299e5e218480a4ecac129b677bd69ae7bbcdde98d48b15d6f098f54fa7b50c5135b8d483085b77d0323c4032f29756b9b77626ea
7
- data.tar.gz: b9d33fe3736de66b49338b70dbde2efd49bae38146c4bd3330a5cce97cf230715cc0db27b1efce4e6cb6e005d14df6030cc74427b00e7ef9bcccf46f7b171b01
6
+ metadata.gz: ea45bb1e825b2c17a9d0965a5bfc33e0a42c9c6eb8a29ae3f0500b6de197dc124ba6a99c8e41b6692fa04d3086f7ba1dbb359d9ff0c4a53520246a598719d2f2
7
+ data.tar.gz: 6e8780a82c7e59aa9c1f0d6283b7c5f09ea5bb66dda6209aa85cef3bec3ccf1079d4a3cc113b33403e6ee274403f1f0c021f892c871123a255985965b017d798
data/README.md CHANGED
@@ -11,3 +11,7 @@ Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and
11
11
  ## License
12
12
 
13
13
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
14
+
15
+ ## Acknowledgments for [rb-readline](https://github.com/ConnorAtherton/rb-readline)
16
+
17
+ In developing Reline, we have used some of the rb-readline implementation, so this library includes [copyright notice, list of conditions and the disclaimer](license_of_rb-readline) under the 3-Clause BSD License. Reline would never have been developed without rb-readline. Thank you for the tremendous accomplishments.
@@ -36,7 +36,6 @@ module Reline
36
36
  attr_accessor :config
37
37
  attr_accessor :key_stroke
38
38
  attr_accessor :line_editor
39
- attr_accessor :ambiguous_width
40
39
  attr_accessor :last_incremental_search
41
40
  attr_reader :output
42
41
 
@@ -44,6 +43,7 @@ module Reline
44
43
  self.output = STDOUT
45
44
  yield self
46
45
  @completion_quote_character = nil
46
+ @bracketed_paste_finished = false
47
47
  end
48
48
 
49
49
  def encoding
@@ -199,7 +199,11 @@ module Reline
199
199
 
200
200
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
201
201
  if ENV['RELINE_STDERR_TTY']
202
- $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
202
+ if Reline::IOGate.win?
203
+ $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
204
+ else
205
+ $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
206
+ end
203
207
  $stderr.sync = true
204
208
  $stderr.puts "Reline is used by #{Process.pid}"
205
209
  end
@@ -243,6 +247,10 @@ module Reline
243
247
  line_editor.input_key(c)
244
248
  line_editor.rerender
245
249
  }
250
+ if @bracketed_paste_finished
251
+ line_editor.rerender_all
252
+ @bracketed_paste_finished = false
253
+ end
246
254
  }
247
255
  if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
248
256
  prev_pasting_state = false
@@ -275,8 +283,13 @@ module Reline
275
283
  buffer = []
276
284
  loop do
277
285
  c = Reline::IOGate.getc
278
- buffer << c
279
- result = key_stroke.match_status(buffer)
286
+ if c == -1
287
+ result = :unmatched
288
+ @bracketed_paste_finished = true
289
+ else
290
+ buffer << c
291
+ result = key_stroke.match_status(buffer)
292
+ end
280
293
  case result
281
294
  when :matched
282
295
  expanded = key_stroke.expand(buffer).map{ |expanded_c|
@@ -342,9 +355,14 @@ module Reline
342
355
  end
343
356
  end
344
357
 
358
+ def ambiguous_width
359
+ may_req_ambiguous_char_width unless defined? @ambiguous_width
360
+ @ambiguous_width
361
+ end
362
+
345
363
  private def may_req_ambiguous_char_width
346
364
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
347
- return if ambiguous_width
365
+ return if @ambiguous_width
348
366
  Reline::IOGate.move_cursor_column(0)
349
367
  begin
350
368
  output.write "\u{25bd}"
@@ -1,4 +1,5 @@
1
1
  require 'io/console'
2
+ require 'timeout'
2
3
 
3
4
  class Reline::ANSI
4
5
  def self.encoding
@@ -67,7 +68,7 @@ class Reline::ANSI
67
68
  end
68
69
 
69
70
  @@buf = []
70
- def self.getc
71
+ def self.inner_getc
71
72
  unless @@buf.empty?
72
73
  return @@buf.shift
73
74
  end
@@ -80,8 +81,48 @@ class Reline::ANSI
80
81
  nil
81
82
  end
82
83
 
84
+ @@in_bracketed_paste_mode = false
85
+ START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
86
+ END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
87
+ def self.getc_with_bracketed_paste
88
+ buffer = String.new(encoding: Encoding::ASCII_8BIT)
89
+ buffer << inner_getc
90
+ while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
91
+ if START_BRACKETED_PASTE == buffer
92
+ @@in_bracketed_paste_mode = true
93
+ return inner_getc
94
+ elsif END_BRACKETED_PASTE == buffer
95
+ @@in_bracketed_paste_mode = false
96
+ ungetc(-1)
97
+ return inner_getc
98
+ end
99
+ begin
100
+ succ_c = nil
101
+ Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
102
+ succ_c = inner_getc
103
+ }
104
+ rescue Timeout::Error
105
+ break
106
+ else
107
+ buffer << succ_c
108
+ end
109
+ end
110
+ buffer.bytes.reverse_each do |ch|
111
+ ungetc ch
112
+ end
113
+ inner_getc
114
+ end
115
+
116
+ def self.getc
117
+ if Reline.core.config.enable_bracketed_paste
118
+ getc_with_bracketed_paste
119
+ else
120
+ inner_getc
121
+ end
122
+ end
123
+
83
124
  def self.in_pasting?
84
- not Reline::IOGate.empty_buffer?
125
+ @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
85
126
  end
86
127
 
87
128
  def self.empty_buffer?
@@ -131,7 +172,7 @@ class Reline::ANSI
131
172
 
132
173
  def self.cursor_pos
133
174
  begin
134
- res = ''
175
+ res = +''
135
176
  m = nil
136
177
  @@input.raw do |stdin|
137
178
  @@output << "\e[6n"
@@ -34,9 +34,11 @@ class Reline::Config
34
34
  show-all-if-unmodified
35
35
  visible-stats
36
36
  show-mode-in-prompt
37
- vi-cmd-mode-icon
38
- vi-ins-mode-icon
37
+ vi-cmd-mode-string
38
+ vi-ins-mode-string
39
39
  emacs-mode-string
40
+ enable-bracketed-paste
41
+ isearch-terminators
40
42
  }
41
43
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
42
44
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -54,8 +56,8 @@ class Reline::Config
54
56
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
55
57
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
56
58
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
- @vi_cmd_mode_icon = '(cmd)'
58
- @vi_ins_mode_icon = '(ins)'
59
+ @vi_cmd_mode_string = '(cmd)'
60
+ @vi_ins_mode_string = '(ins)'
59
61
  @emacs_mode_string = '@'
60
62
  # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
61
63
  @history_size = -1 # unlimited
@@ -237,7 +239,7 @@ class Reline::Config
237
239
  when 'completion-query-items'
238
240
  @completion_query_items = value.to_i
239
241
  when 'isearch-terminators'
240
- @isearch_terminators = instance_eval(value)
242
+ @isearch_terminators = retrieve_string(value)
241
243
  when 'editing-mode'
242
244
  case value
243
245
  when 'emacs'
@@ -268,9 +270,9 @@ class Reline::Config
268
270
  @show_mode_in_prompt = false
269
271
  end
270
272
  when 'vi-cmd-mode-string'
271
- @vi_cmd_mode_icon = retrieve_string(value)
273
+ @vi_cmd_mode_string = retrieve_string(value)
272
274
  when 'vi-ins-mode-string'
273
- @vi_ins_mode_icon = retrieve_string(value)
275
+ @vi_ins_mode_string = retrieve_string(value)
274
276
  when 'emacs-mode-string'
275
277
  @emacs_mode_string = retrieve_string(value)
276
278
  when *VARIABLE_NAMES then
@@ -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_string
72
+ mode_string = nil
73
+ if @config.show_mode_in_prompt
74
+ if @config.editing_mode_is?(:vi_command)
75
+ mode_string = @config.vi_cmd_mode_string
76
+ elsif @config.editing_mode_is?(:vi_insert)
77
+ mode_string = @config.vi_ins_mode_string
78
+ elsif @config.editing_mode_is?(:emacs)
79
+ mode_string = @config.emacs_mode_string
80
+ else
81
+ mode_string = '?'
82
+ end
83
+ end
84
+ if mode_string != @prev_mode_string
85
+ @rerender_all = true
86
+ end
87
+ @prev_mode_string = mode_string
88
+ mode_string
89
+ end
90
+
67
91
  private def check_multiline_prompt(buffer, prompt)
68
92
  if @vi_arg
69
93
  prompt = "(arg: #{@vi_arg}) "
@@ -74,39 +98,44 @@ class Reline::LineEditor
74
98
  else
75
99
  prompt = @prompt
76
100
  end
77
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
101
+ if simplified_rendering?
102
+ mode_string = check_mode_string
103
+ prompt = mode_string + prompt if mode_string
104
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
105
+ end
78
106
  if @prompt_proc
79
- prompt_list = @prompt_proc.(buffer)
80
- prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
81
- if @config.show_mode_in_prompt
82
- if @config.editing_mode_is?(:vi_command)
83
- mode_icon = @config.vi_cmd_mode_icon
84
- elsif @config.editing_mode_is?(:vi_insert)
85
- mode_icon = @config.vi_ins_mode_icon
86
- elsif @config.editing_mode_is?(:emacs)
87
- mode_icon = @config.emacs_mode_string
88
- else
89
- mode_icon = '?'
107
+ use_cached_prompt_list = false
108
+ if @cached_prompt_list
109
+ if @just_cursor_moving
110
+ use_cached_prompt_list = true
111
+ elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
112
+ use_cached_prompt_list = true
90
113
  end
91
- prompt_list.map!{ |pr| mode_icon + pr }
92
114
  end
115
+ use_cached_prompt_list = false if @rerender_all
116
+ if use_cached_prompt_list
117
+ prompt_list = @cached_prompt_list
118
+ else
119
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
120
+ @prompt_cache_time = Time.now.to_f
121
+ end
122
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
123
+ mode_string = check_mode_string
124
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
93
125
  prompt = prompt_list[@line_index]
126
+ prompt = prompt_list[0] if prompt.nil?
127
+ prompt = prompt_list.last if prompt.nil?
128
+ if buffer.size > prompt_list.size
129
+ (buffer.size - prompt_list.size).times do
130
+ prompt_list << prompt_list.last
131
+ end
132
+ end
94
133
  prompt_width = calculate_width(prompt, true)
95
134
  [prompt, prompt_width, prompt_list]
96
135
  else
136
+ mode_string = check_mode_string
137
+ prompt = mode_string + prompt if mode_string
97
138
  prompt_width = calculate_width(prompt, true)
98
- if @config.show_mode_in_prompt
99
- if @config.editing_mode_is?(:vi_command)
100
- mode_icon = @config.vi_cmd_mode_icon
101
- elsif @config.editing_mode_is?(:vi_insert)
102
- mode_icon = @config.vi_ins_mode_icon
103
- elsif @config.editing_mode_is?(:emacs)
104
- mode_icon = @config.emacs_mode_string
105
- else
106
- mode_icon = '?'
107
- end
108
- prompt = mode_icon + prompt
109
- end
110
139
  [prompt, prompt_width, nil]
111
140
  end
112
141
  end
@@ -114,6 +143,7 @@ class Reline::LineEditor
114
143
  def reset(prompt = '', encoding:)
115
144
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
116
145
  @screen_size = Reline::IOGate.get_screen_size
146
+ @screen_height = @screen_size.first
117
147
  reset_variables(prompt, encoding: encoding)
118
148
  @old_trap = Signal.trap('SIGINT') {
119
149
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
@@ -123,6 +153,7 @@ class Reline::LineEditor
123
153
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
124
154
  old_screen_size = @screen_size
125
155
  @screen_size = Reline::IOGate.get_screen_size
156
+ @screen_height = @screen_size.first
126
157
  if old_screen_size.last < @screen_size.last # columns increase
127
158
  @rerender_all = true
128
159
  rerender
@@ -174,11 +205,12 @@ class Reline::LineEditor
174
205
  @cleared = false
175
206
  @rerender_all = false
176
207
  @history_pointer = nil
177
- @kill_ring = Reline::KillRing.new
208
+ @kill_ring ||= Reline::KillRing.new
178
209
  @vi_clipboard = ''
179
210
  @vi_arg = nil
180
211
  @waiting_proc = nil
181
212
  @waiting_operator_proc = nil
213
+ @waiting_operator_vi_arg = nil
182
214
  @completion_journey_data = nil
183
215
  @completion_state = CompletionState::NORMAL
184
216
  @perfect_matched = nil
@@ -186,8 +218,15 @@ class Reline::LineEditor
186
218
  @first_prompt = true
187
219
  @searching_prompt = nil
188
220
  @first_char = true
221
+ @add_newline_to_end_of_buffer = false
222
+ @just_cursor_moving = nil
223
+ @cached_prompt_list = nil
224
+ @prompt_cache_time = nil
189
225
  @eof = false
190
226
  @continuous_insertion_buffer = String.new(encoding: @encoding)
227
+ @scroll_partial_screen = nil
228
+ @prev_mode_string = nil
229
+ @drop_terminate_spaces = false
191
230
  reset_line
192
231
  end
193
232
 
@@ -232,6 +271,7 @@ class Reline::LineEditor
232
271
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
233
272
  @previous_line_index = @line_index
234
273
  @line_index += 1
274
+ @just_cursor_moving = false
235
275
  end
236
276
 
237
277
  private def calculate_height_by_width(width)
@@ -272,28 +312,28 @@ class Reline::LineEditor
272
312
  end
273
313
  end
274
314
 
275
- private def calculate_nearest_cursor
276
- @cursor_max = calculate_width(line)
315
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
316
+ new_cursor_max = calculate_width(line_to_calc)
277
317
  new_cursor = 0
278
318
  new_byte_pointer = 0
279
319
  height = 1
280
320
  max_width = @screen_size.last
281
321
  if @config.editing_mode_is?(:vi_command)
282
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
322
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
283
323
  if last_byte_size > 0
284
- last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
324
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
285
325
  last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
286
- cursor_max = @cursor_max - last_width
326
+ end_of_line_cursor = new_cursor_max - last_width
287
327
  else
288
- cursor_max = @cursor_max
328
+ end_of_line_cursor = new_cursor_max
289
329
  end
290
330
  else
291
- cursor_max = @cursor_max
331
+ end_of_line_cursor = new_cursor_max
292
332
  end
293
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
333
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
294
334
  mbchar_width = Reline::Unicode.get_mbchar_width(gc)
295
335
  now = new_cursor + mbchar_width
296
- if now > cursor_max or now > @cursor
336
+ if now > end_of_line_cursor or now > cursor
297
337
  break
298
338
  end
299
339
  new_cursor += mbchar_width
@@ -302,13 +342,20 @@ class Reline::LineEditor
302
342
  end
303
343
  new_byte_pointer += gc.bytesize
304
344
  end
305
- @started_from = height - 1
306
- @cursor = new_cursor
307
- @byte_pointer = new_byte_pointer
345
+ new_started_from = height - 1
346
+ if update
347
+ @cursor = new_cursor
348
+ @cursor_max = new_cursor_max
349
+ @started_from = new_started_from
350
+ @byte_pointer = new_byte_pointer
351
+ else
352
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
353
+ end
308
354
  end
309
355
 
310
356
  def rerender_all
311
357
  @rerender_all = true
358
+ process_insert(force: true)
312
359
  rerender
313
360
  end
314
361
 
@@ -317,168 +364,53 @@ class Reline::LineEditor
317
364
  if @menu_info
318
365
  scroll_down(@highest_in_all - @first_line_started_from)
319
366
  @rerender_all = true
320
- @menu_info.list.sort!.each do |item|
321
- Reline::IOGate.move_cursor_column(0)
322
- @output.write item
323
- @output.flush
324
- scroll_down(1)
325
- end
326
- scroll_down(@highest_in_all - 1)
327
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
367
+ end
368
+ if @menu_info
369
+ show_menu
328
370
  @menu_info = nil
329
371
  end
330
372
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
331
373
  if @cleared
332
- Reline::IOGate.clear_screen
374
+ clear_screen_buffer(prompt, prompt_list, prompt_width)
333
375
  @cleared = false
334
- back = 0
335
- modify_lines(whole_lines).each_with_index do |line, index|
336
- if @prompt_proc
337
- pr = prompt_list[index]
338
- height = render_partial(pr, calculate_width(pr), line, false)
339
- else
340
- height = render_partial(prompt, prompt_width, line, false)
341
- end
342
- if index < (@buffer_of_lines.size - 1)
343
- move_cursor_down(height)
344
- back += height
345
- end
346
- end
347
- move_cursor_up(back)
348
- move_cursor_down(@first_line_started_from + @started_from)
349
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
350
376
  return
351
377
  end
352
378
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
353
379
  # FIXME: end of logical line sometimes breaks
354
- if @previous_line_index or new_highest_in_this != @highest_in_this
355
- if @previous_line_index
356
- new_lines = whole_lines(index: @previous_line_index, line: @line)
357
- else
358
- new_lines = whole_lines
359
- end
360
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
361
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
362
- diff = all_height - @highest_in_all
363
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
364
- if diff > 0
365
- scroll_down(diff)
366
- move_cursor_up(all_height - 1)
367
- elsif diff < 0
368
- (-diff).times do
369
- Reline::IOGate.move_cursor_column(0)
370
- Reline::IOGate.erase_after_cursor
371
- move_cursor_up(1)
372
- end
373
- move_cursor_up(all_height - 1)
380
+ if @add_newline_to_end_of_buffer
381
+ rerender_added_newline
382
+ @add_newline_to_end_of_buffer = false
383
+ else
384
+ if @just_cursor_moving and not @rerender_all
385
+ rendered = just_move_cursor
386
+ @just_cursor_moving = false
387
+ return
388
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
389
+ rerender_changed_current_line
390
+ @previous_line_index = nil
391
+ rendered = true
392
+ elsif @rerender_all
393
+ rerender_all_lines
394
+ @rerender_all = false
395
+ rendered = true
374
396
  else
375
- move_cursor_up(all_height - 1)
376
- end
377
- @highest_in_all = all_height
378
- back = 0
379
- modify_lines(new_lines).each_with_index do |line, index|
380
- if @prompt_proc
381
- prompt = prompt_list[index]
382
- prompt_width = calculate_width(prompt, true)
383
- end
384
- height = render_partial(prompt, prompt_width, line, false)
385
- if index < (new_lines.size - 1)
386
- scroll_down(1)
387
- back += height
388
- else
389
- back += height - 1
390
- end
391
397
  end
392
- move_cursor_up(back)
393
- if @previous_line_index
394
- @buffer_of_lines[@previous_line_index] = @line
395
- @line = @buffer_of_lines[@line_index]
396
- end
397
- @first_line_started_from =
398
- if @line_index.zero?
399
- 0
400
- else
401
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
402
- end
403
- if @prompt_proc
404
- prompt = prompt_list[@line_index]
405
- prompt_width = calculate_width(prompt, true)
406
- end
407
- move_cursor_down(@first_line_started_from)
408
- calculate_nearest_cursor
409
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
410
- move_cursor_down(@started_from)
411
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
412
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
413
- @previous_line_index = nil
414
- rendered = true
415
- elsif @rerender_all
416
- move_cursor_up(@first_line_started_from + @started_from)
417
- Reline::IOGate.move_cursor_column(0)
418
- back = 0
419
- new_buffer = whole_lines
420
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
421
- new_buffer.each_with_index do |line, index|
422
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
423
- width = prompt_width + calculate_width(line)
424
- height = calculate_height_by_width(width)
425
- back += height
426
- end
427
- if back > @highest_in_all
428
- scroll_down(back - 1)
429
- move_cursor_up(back - 1)
430
- elsif back < @highest_in_all
431
- scroll_down(back)
432
- Reline::IOGate.erase_after_cursor
433
- (@highest_in_all - back - 1).times do
434
- scroll_down(1)
435
- Reline::IOGate.erase_after_cursor
436
- end
437
- move_cursor_up(@highest_in_all - 1)
438
- end
439
- modify_lines(new_buffer).each_with_index do |line, index|
440
- if @prompt_proc
441
- prompt = prompt_list[index]
442
- prompt_width = calculate_width(prompt, true)
443
- end
444
- render_partial(prompt, prompt_width, line, false)
445
- if index < (new_buffer.size - 1)
446
- move_cursor_down(1)
447
- end
448
- end
449
- move_cursor_up(back - 1)
450
- if @prompt_proc
451
- prompt = prompt_list[@line_index]
452
- prompt_width = calculate_width(prompt, true)
453
- end
454
- @highest_in_all = back
455
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
456
- @first_line_started_from =
457
- if @line_index.zero?
458
- 0
459
- else
460
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
461
- end
462
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
463
- move_cursor_down(@first_line_started_from + @started_from)
464
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
465
- @rerender_all = false
466
- rendered = true
467
398
  end
468
399
  line = modify_lines(whole_lines)[@line_index]
469
400
  if @is_multiline
470
401
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
471
402
  if finished?
472
403
  # Always rerender on finish because output_modifier_proc may return a different output.
473
- render_partial(prompt, prompt_width, line)
404
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
474
405
  scroll_down(1)
475
406
  Reline::IOGate.move_cursor_column(0)
476
407
  Reline::IOGate.erase_after_cursor
477
408
  elsif not rendered
478
- render_partial(prompt, prompt_width, line)
409
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
479
410
  end
411
+ @buffer_of_lines[@line_index] = @line
480
412
  else
481
- render_partial(prompt, prompt_width, line)
413
+ render_partial(prompt, prompt_width, line, 0)
482
414
  if finished?
483
415
  scroll_down(1)
484
416
  Reline::IOGate.move_cursor_column(0)
@@ -487,8 +419,239 @@ class Reline::LineEditor
487
419
  end
488
420
  end
489
421
 
490
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
422
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
423
+ if @screen_height < highest_in_all
424
+ old_scroll_partial_screen = @scroll_partial_screen
425
+ if cursor_y == 0
426
+ @scroll_partial_screen = 0
427
+ elsif cursor_y == (highest_in_all - 1)
428
+ @scroll_partial_screen = highest_in_all - @screen_height
429
+ else
430
+ if @scroll_partial_screen
431
+ if cursor_y <= @scroll_partial_screen
432
+ @scroll_partial_screen = cursor_y
433
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
434
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
435
+ end
436
+ else
437
+ if cursor_y > (@screen_height - 1)
438
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
439
+ else
440
+ @scroll_partial_screen = 0
441
+ end
442
+ end
443
+ end
444
+ if @scroll_partial_screen != old_scroll_partial_screen
445
+ @rerender_all = true
446
+ end
447
+ else
448
+ if @scroll_partial_screen
449
+ @rerender_all = true
450
+ end
451
+ @scroll_partial_screen = nil
452
+ end
453
+ end
454
+
455
+ private def rerender_added_newline
456
+ scroll_down(1)
457
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
458
+ prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
459
+ @buffer_of_lines[@previous_line_index] = @line
460
+ @line = @buffer_of_lines[@line_index]
461
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
462
+ @cursor = @cursor_max = calculate_width(@line)
463
+ @byte_pointer = @line.bytesize
464
+ @highest_in_all += @highest_in_this
465
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
466
+ @first_line_started_from += @started_from + 1
467
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
468
+ @previous_line_index = nil
469
+ end
470
+
471
+ def just_move_cursor
472
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
473
+ move_cursor_up(@started_from)
474
+ new_first_line_started_from =
475
+ if @line_index.zero?
476
+ 0
477
+ else
478
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
479
+ end
480
+ first_line_diff = new_first_line_started_from - @first_line_started_from
481
+ new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
482
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
483
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
484
+ @previous_line_index = nil
485
+ if @rerender_all
486
+ @line = @buffer_of_lines[@line_index]
487
+ rerender_all_lines
488
+ @rerender_all = false
489
+ true
490
+ else
491
+ @line = @buffer_of_lines[@line_index]
492
+ @first_line_started_from = new_first_line_started_from
493
+ @started_from = new_started_from
494
+ @cursor = new_cursor
495
+ @cursor_max = new_cursor_max
496
+ @byte_pointer = new_byte_pointer
497
+ move_cursor_down(first_line_diff + @started_from)
498
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
499
+ false
500
+ end
501
+ end
502
+
503
+ private def rerender_changed_current_line
504
+ if @previous_line_index
505
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
506
+ else
507
+ new_lines = whole_lines
508
+ end
509
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
510
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
511
+ diff = all_height - @highest_in_all
512
+ move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
513
+ if diff > 0
514
+ scroll_down(diff)
515
+ move_cursor_up(all_height - 1)
516
+ elsif diff < 0
517
+ (-diff).times do
518
+ Reline::IOGate.move_cursor_column(0)
519
+ Reline::IOGate.erase_after_cursor
520
+ move_cursor_up(1)
521
+ end
522
+ move_cursor_up(all_height - 1)
523
+ else
524
+ move_cursor_up(all_height - 1)
525
+ end
526
+ @highest_in_all = all_height
527
+ back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
528
+ move_cursor_up(back)
529
+ if @previous_line_index
530
+ @buffer_of_lines[@previous_line_index] = @line
531
+ @line = @buffer_of_lines[@line_index]
532
+ end
533
+ @first_line_started_from =
534
+ if @line_index.zero?
535
+ 0
536
+ else
537
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
538
+ end
539
+ if @prompt_proc
540
+ prompt = prompt_list[@line_index]
541
+ prompt_width = calculate_width(prompt, true)
542
+ end
543
+ move_cursor_down(@first_line_started_from)
544
+ calculate_nearest_cursor
545
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
546
+ move_cursor_down(@started_from)
547
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
548
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
549
+ end
550
+
551
+ private def rerender_all_lines
552
+ move_cursor_up(@first_line_started_from + @started_from)
553
+ Reline::IOGate.move_cursor_column(0)
554
+ back = 0
555
+ new_buffer = whole_lines
556
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
557
+ new_buffer.each_with_index do |line, index|
558
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
559
+ width = prompt_width + calculate_width(line)
560
+ height = calculate_height_by_width(width)
561
+ back += height
562
+ end
563
+ old_highest_in_all = @highest_in_all
564
+ if @line_index.zero?
565
+ new_first_line_started_from = 0
566
+ else
567
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
568
+ end
569
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
570
+ if back > old_highest_in_all
571
+ scroll_down(back - 1)
572
+ move_cursor_up(back - 1)
573
+ elsif back < old_highest_in_all
574
+ scroll_down(back)
575
+ Reline::IOGate.erase_after_cursor
576
+ (old_highest_in_all - back - 1).times do
577
+ scroll_down(1)
578
+ Reline::IOGate.erase_after_cursor
579
+ end
580
+ move_cursor_up(old_highest_in_all - 1)
581
+ end
582
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
583
+ render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
584
+ if @prompt_proc
585
+ prompt = prompt_list[@line_index]
586
+ prompt_width = calculate_width(prompt, true)
587
+ end
588
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
589
+ @highest_in_all = back
590
+ @first_line_started_from = new_first_line_started_from
591
+ @started_from = new_started_from
592
+ if @scroll_partial_screen
593
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
594
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
595
+ else
596
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
597
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
598
+ end
599
+ end
600
+
601
+ private def render_whole_lines(lines, prompt, prompt_width)
602
+ rendered_height = 0
603
+ modify_lines(lines).each_with_index do |line, index|
604
+ if prompt.is_a?(Array)
605
+ line_prompt = prompt[index]
606
+ prompt_width = calculate_width(line_prompt, true)
607
+ else
608
+ line_prompt = prompt
609
+ end
610
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
611
+ if index < (lines.size - 1)
612
+ if @scroll_partial_screen
613
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
614
+ move_cursor_down(1)
615
+ end
616
+ else
617
+ scroll_down(1)
618
+ end
619
+ rendered_height += height
620
+ else
621
+ rendered_height += height - 1
622
+ end
623
+ end
624
+ rendered_height
625
+ end
626
+
627
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
491
628
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
629
+ cursor_up_from_last_line = 0
630
+ # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
631
+ if @scroll_partial_screen
632
+ last_visual_line = this_started_from + (height - 1)
633
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
634
+ if (@scroll_partial_screen - this_started_from) >= height
635
+ # Render nothing because this line is before the screen.
636
+ visual_lines = []
637
+ elsif this_started_from > last_screen_line
638
+ # Render nothing because this line is after the screen.
639
+ visual_lines = []
640
+ else
641
+ deleted_lines_before_screen = []
642
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
643
+ # A part of visual lines are before the screen.
644
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
645
+ deleted_lines_before_screen.compact!
646
+ end
647
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
648
+ # A part of visual lines are after the screen.
649
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
650
+ end
651
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
652
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
653
+ end
654
+ end
492
655
  if with_control
493
656
  if height > @highest_in_this
494
657
  diff = height - @highest_in_this
@@ -502,10 +665,14 @@ class Reline::LineEditor
502
665
  @highest_in_this = height
503
666
  end
504
667
  move_cursor_up(@started_from)
668
+ cursor_up_from_last_line = height - 1 - @started_from
505
669
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
506
670
  end
507
- Reline::IOGate.move_cursor_column(0)
671
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
672
+ @output.write "\e[0m" # clear character decorations
673
+ end
508
674
  visual_lines.each_with_index do |line, index|
675
+ Reline::IOGate.move_cursor_column(0)
509
676
  if line.nil?
510
677
  if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
511
678
  # reaches the end of line
@@ -537,15 +704,18 @@ class Reline::LineEditor
537
704
  @pre_input_hook&.call
538
705
  end
539
706
  end
540
- Reline::IOGate.erase_after_cursor
541
- Reline::IOGate.move_cursor_column(0)
707
+ unless visual_lines.empty?
708
+ Reline::IOGate.erase_after_cursor
709
+ Reline::IOGate.move_cursor_column(0)
710
+ end
542
711
  if with_control
543
712
  # Just after rendring, so the cursor is on the last line.
544
713
  if finished?
545
714
  Reline::IOGate.move_cursor_column(0)
546
715
  else
547
716
  # Moves up from bottom of lines to the cursor position.
548
- move_cursor_up(height - 1 - @started_from)
717
+ move_cursor_up(cursor_up_from_last_line)
718
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
549
719
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
550
720
  end
551
721
  end
@@ -562,6 +732,39 @@ class Reline::LineEditor
562
732
  end
563
733
  end
564
734
 
735
+ private def show_menu
736
+ scroll_down(@highest_in_all - @first_line_started_from)
737
+ @rerender_all = true
738
+ @menu_info.list.sort!.each do |item|
739
+ Reline::IOGate.move_cursor_column(0)
740
+ @output.write item
741
+ @output.flush
742
+ scroll_down(1)
743
+ end
744
+ scroll_down(@highest_in_all - 1)
745
+ move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
746
+ end
747
+
748
+ private def clear_screen_buffer(prompt, prompt_list, prompt_width)
749
+ Reline::IOGate.clear_screen
750
+ back = 0
751
+ modify_lines(whole_lines).each_with_index do |line, index|
752
+ if @prompt_proc
753
+ pr = prompt_list[index]
754
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
755
+ else
756
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
757
+ end
758
+ if index < (@buffer_of_lines.size - 1)
759
+ move_cursor_down(height)
760
+ back += height
761
+ end
762
+ end
763
+ move_cursor_up(back)
764
+ move_cursor_down(@first_line_started_from + @started_from)
765
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
766
+ end
767
+
565
768
  def editing_mode
566
769
  @config.editing_mode
567
770
  end
@@ -698,7 +901,8 @@ class Reline::LineEditor
698
901
  if @waiting_operator_proc
699
902
  if VI_MOTIONS.include?(method_symbol)
700
903
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
701
- block.()
904
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
905
+ block.(true)
702
906
  unless @waiting_proc
703
907
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
704
908
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
@@ -706,38 +910,55 @@ class Reline::LineEditor
706
910
  else
707
911
  old_waiting_proc = @waiting_proc
708
912
  old_waiting_operator_proc = @waiting_operator_proc
913
+ current_waiting_operator_proc = @waiting_operator_proc
709
914
  @waiting_proc = proc { |k|
710
915
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
711
916
  old_waiting_proc.(k)
712
917
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
713
918
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
714
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
919
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
715
920
  @waiting_operator_proc = old_waiting_operator_proc
716
921
  }
717
922
  end
718
923
  else
719
924
  # Ignores operator when not motion is given.
720
- block.()
925
+ block.(false)
721
926
  end
722
927
  @waiting_operator_proc = nil
928
+ @waiting_operator_vi_arg = nil
929
+ @vi_arg = nil
723
930
  else
724
- block.()
931
+ block.(false)
725
932
  end
726
933
  end
727
934
 
728
935
  private def argumentable?(method_obj)
729
- method_obj and method_obj.parameters.length != 1
936
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
937
+ end
938
+
939
+ private def inclusive?(method_obj)
940
+ # If a motion method with the keyword argument "inclusive" follows the
941
+ # operator, it must contain the character at the cursor position.
942
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
730
943
  end
731
944
 
732
- def wrap_method_call(method_symbol, method_obj, key)
945
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
733
946
  if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
734
947
  not_insertion = method_symbol != :ed_insert
735
948
  process_insert(force: not_insertion)
736
949
  end
737
- if @vi_arg
738
- method_obj.(key, arg: @vi_arg)
950
+ if @vi_arg and argumentable?(method_obj)
951
+ if with_operator and inclusive?(method_obj)
952
+ method_obj.(key, arg: @vi_arg, inclusive: true)
953
+ else
954
+ method_obj.(key, arg: @vi_arg)
955
+ end
739
956
  else
740
- method_obj.(key)
957
+ if with_operator and inclusive?(method_obj)
958
+ method_obj.(key, inclusive: true)
959
+ else
960
+ method_obj.(key)
961
+ end
741
962
  end
742
963
  end
743
964
 
@@ -749,8 +970,8 @@ class Reline::LineEditor
749
970
  end
750
971
  if method_symbol and key.is_a?(Symbol)
751
972
  if @vi_arg and argumentable?(method_obj)
752
- run_for_operators(key, method_symbol) do
753
- wrap_method_call(method_symbol, method_obj, key)
973
+ run_for_operators(key, method_symbol) do |with_operator|
974
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
754
975
  end
755
976
  else
756
977
  wrap_method_call(method_symbol, method_obj, key) if method_obj
@@ -762,15 +983,15 @@ class Reline::LineEditor
762
983
  ed_argument_digit(key)
763
984
  else
764
985
  if argumentable?(method_obj)
765
- run_for_operators(key, method_symbol) do
766
- wrap_method_call(method_symbol, method_obj, key)
986
+ run_for_operators(key, method_symbol) do |with_operator|
987
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
767
988
  end
768
989
  elsif @waiting_proc
769
990
  @waiting_proc.(key)
770
991
  elsif method_obj
771
992
  wrap_method_call(method_symbol, method_obj, key)
772
993
  else
773
- ed_insert(key)
994
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
774
995
  end
775
996
  @kill_ring.process
776
997
  @vi_arg = nil
@@ -782,13 +1003,13 @@ class Reline::LineEditor
782
1003
  if method_symbol == :ed_argument_digit
783
1004
  wrap_method_call(method_symbol, method_obj, key)
784
1005
  else
785
- run_for_operators(key, method_symbol) do
786
- wrap_method_call(method_symbol, method_obj, key)
1006
+ run_for_operators(key, method_symbol) do |with_operator|
1007
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
787
1008
  end
788
1009
  end
789
1010
  @kill_ring.process
790
1011
  else
791
- ed_insert(key)
1012
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
792
1013
  end
793
1014
  end
794
1015
 
@@ -831,6 +1052,7 @@ class Reline::LineEditor
831
1052
  end
832
1053
 
833
1054
  def input_key(key)
1055
+ @just_cursor_moving = nil
834
1056
  if key.char.nil?
835
1057
  if @first_char
836
1058
  @line = nil
@@ -838,6 +1060,7 @@ class Reline::LineEditor
838
1060
  finish
839
1061
  return
840
1062
  end
1063
+ old_line = @line.dup
841
1064
  @first_char = false
842
1065
  completion_occurs = false
843
1066
  if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
@@ -866,6 +1089,17 @@ class Reline::LineEditor
866
1089
  unless completion_occurs
867
1090
  @completion_state = CompletionState::NORMAL
868
1091
  end
1092
+ if not Reline::IOGate.in_pasting? and @just_cursor_moving.nil?
1093
+ if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1094
+ @just_cursor_moving = true
1095
+ elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1096
+ @just_cursor_moving = true
1097
+ else
1098
+ @just_cursor_moving = false
1099
+ end
1100
+ else
1101
+ @just_cursor_moving = false
1102
+ end
869
1103
  if @is_multiline and @auto_indent_proc and not simplified_rendering?
870
1104
  process_auto_indent
871
1105
  end
@@ -904,6 +1138,7 @@ class Reline::LineEditor
904
1138
  new_lines = whole_lines
905
1139
  end
906
1140
  new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1141
+ new_indent = @cursor_max if new_indent&.> @cursor_max
907
1142
  if new_indent&.>= 0
908
1143
  md = new_lines[@line_index].match(/\A */)
909
1144
  prev_indent = md[0].count(' ')
@@ -1097,11 +1332,14 @@ class Reline::LineEditor
1097
1332
 
1098
1333
  private def key_newline(key)
1099
1334
  if @is_multiline
1335
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1336
+ @add_newline_to_end_of_buffer = true
1337
+ end
1100
1338
  next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1101
1339
  cursor_line = @line.byteslice(0, @byte_pointer)
1102
1340
  insert_new_line(cursor_line, next_line)
1103
1341
  @cursor = 0
1104
- @check_new_auto_indent = true
1342
+ @check_new_auto_indent = true unless Reline::IOGate.in_pasting?
1105
1343
  end
1106
1344
  end
1107
1345
 
@@ -1146,6 +1384,8 @@ class Reline::LineEditor
1146
1384
  if Reline::IOGate.in_pasting?
1147
1385
  @continuous_insertion_buffer << str
1148
1386
  return
1387
+ elsif not @continuous_insertion_buffer.empty?
1388
+ process_insert
1149
1389
  end
1150
1390
  width = Reline::Unicode.get_mbchar_width(str)
1151
1391
  if @cursor == @cursor_max
@@ -1153,7 +1393,12 @@ class Reline::LineEditor
1153
1393
  else
1154
1394
  @line = byteinsert(@line, @byte_pointer, str)
1155
1395
  end
1396
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1156
1397
  @byte_pointer += bytesize
1398
+ last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1399
+ if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1400
+ width = 0
1401
+ end
1157
1402
  @cursor += width
1158
1403
  @cursor_max += width
1159
1404
  end
@@ -1212,6 +1457,7 @@ class Reline::LineEditor
1212
1457
  arg -= 1
1213
1458
  ed_prev_char(key, arg: arg) if arg > 0
1214
1459
  end
1460
+ alias_method :backward_char, :ed_prev_char
1215
1461
 
1216
1462
  private def vi_first_print(key)
1217
1463
  @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
@@ -1364,9 +1610,11 @@ class Reline::LineEditor
1364
1610
  searcher = generate_searcher
1365
1611
  searcher.resume(key)
1366
1612
  @searching_prompt = "(reverse-i-search)`': "
1613
+ termination_keys = ["\C-j".ord]
1614
+ termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1367
1615
  @waiting_proc = ->(k) {
1368
1616
  case k
1369
- when "\C-j".ord
1617
+ when *termination_keys
1370
1618
  if @history_pointer
1371
1619
  buffer = Reline::HISTORY[@history_pointer]
1372
1620
  else
@@ -1385,6 +1633,8 @@ class Reline::LineEditor
1385
1633
  @waiting_proc = nil
1386
1634
  @cursor_max = calculate_width(@line)
1387
1635
  @cursor = @byte_pointer = 0
1636
+ @rerender_all = true
1637
+ @cached_prompt_list = nil
1388
1638
  searcher.resume(-1)
1389
1639
  when "\C-g".ord
1390
1640
  if @is_multiline
@@ -1428,6 +1678,8 @@ class Reline::LineEditor
1428
1678
  @waiting_proc = nil
1429
1679
  @cursor_max = calculate_width(@line)
1430
1680
  @cursor = @byte_pointer = 0
1681
+ @rerender_all = true
1682
+ @cached_prompt_list = nil
1431
1683
  searcher.resume(-1)
1432
1684
  end
1433
1685
  end
@@ -1480,7 +1732,7 @@ class Reline::LineEditor
1480
1732
  @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1481
1733
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1482
1734
  @line_index = line_no
1483
- @line = @buffer_of_lines.last
1735
+ @line = @buffer_of_lines[@line_index]
1484
1736
  @rerender_all = true
1485
1737
  else
1486
1738
  @line = Reline::HISTORY[@history_pointer]
@@ -1528,7 +1780,7 @@ class Reline::LineEditor
1528
1780
  @line_index = line_no
1529
1781
  end
1530
1782
  @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1531
- @line = @buffer_of_lines.last
1783
+ @line = @buffer_of_lines[@line_index]
1532
1784
  @rerender_all = true
1533
1785
  else
1534
1786
  if @history_pointer.nil? and substr.empty?
@@ -1720,6 +1972,7 @@ class Reline::LineEditor
1720
1972
  @cursor = 0
1721
1973
  end
1722
1974
  end
1975
+ alias_method :kill_line, :em_kill_line
1723
1976
 
1724
1977
  private def em_delete(key)
1725
1978
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -1770,6 +2023,7 @@ class Reline::LineEditor
1770
2023
  @byte_pointer += yanked.bytesize
1771
2024
  end
1772
2025
  end
2026
+ alias_method :yank, :em_yank
1773
2027
 
1774
2028
  private def em_yank_pop(key)
1775
2029
  yanked, prev_yank = @kill_ring.yank_pop
@@ -1786,6 +2040,7 @@ class Reline::LineEditor
1786
2040
  @byte_pointer += yanked.bytesize
1787
2041
  end
1788
2042
  end
2043
+ alias_method :yank_pop, :em_yank_pop
1789
2044
 
1790
2045
  private def ed_clear_screen(key)
1791
2046
  @cleared = true
@@ -1916,9 +2171,10 @@ class Reline::LineEditor
1916
2171
  @byte_pointer -= byte_size
1917
2172
  @cursor -= width
1918
2173
  @cursor_max -= width
1919
- @kill_ring.append(deleted)
2174
+ @kill_ring.append(deleted, true)
1920
2175
  end
1921
2176
  end
2177
+ alias_method :unix_word_rubout, :em_kill_region
1922
2178
 
1923
2179
  private def copy_for_vi(text)
1924
2180
  if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@@ -1939,11 +2195,11 @@ class Reline::LineEditor
1939
2195
  ed_prev_char(key)
1940
2196
  @config.editing_mode = :vi_command
1941
2197
  end
1942
- alias_method :backward_char, :ed_prev_char
2198
+ alias_method :vi_movement_mode, :vi_command_mode
1943
2199
 
1944
2200
  private def vi_next_word(key, arg: 1)
1945
2201
  if @line.bytesize > @byte_pointer
1946
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer)
2202
+ byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
1947
2203
  @byte_pointer += byte_size
1948
2204
  @cursor += width
1949
2205
  end
@@ -1961,13 +2217,22 @@ class Reline::LineEditor
1961
2217
  vi_prev_word(key, arg: arg) if arg > 0
1962
2218
  end
1963
2219
 
1964
- private def vi_end_word(key, arg: 1)
2220
+ private def vi_end_word(key, arg: 1, inclusive: false)
1965
2221
  if @line.bytesize > @byte_pointer
1966
2222
  byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1967
2223
  @byte_pointer += byte_size
1968
2224
  @cursor += width
1969
2225
  end
1970
2226
  arg -= 1
2227
+ if inclusive and arg.zero?
2228
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2229
+ if byte_size > 0
2230
+ c = @line.byteslice(@byte_pointer, byte_size)
2231
+ width = Reline::Unicode.get_mbchar_width(c)
2232
+ @byte_pointer += byte_size
2233
+ @cursor += width
2234
+ end
2235
+ end
1971
2236
  vi_end_word(key, arg: arg) if arg > 0
1972
2237
  end
1973
2238
 
@@ -1991,13 +2256,22 @@ class Reline::LineEditor
1991
2256
  vi_prev_big_word(key, arg: arg) if arg > 0
1992
2257
  end
1993
2258
 
1994
- private def vi_end_big_word(key, arg: 1)
2259
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1995
2260
  if @line.bytesize > @byte_pointer
1996
2261
  byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1997
2262
  @byte_pointer += byte_size
1998
2263
  @cursor += width
1999
2264
  end
2000
2265
  arg -= 1
2266
+ if inclusive and arg.zero?
2267
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2268
+ if byte_size > 0
2269
+ c = @line.byteslice(@byte_pointer, byte_size)
2270
+ width = Reline::Unicode.get_mbchar_width(c)
2271
+ @byte_pointer += byte_size
2272
+ @cursor += width
2273
+ end
2274
+ end
2001
2275
  vi_end_big_word(key, arg: arg) if arg > 0
2002
2276
  end
2003
2277
 
@@ -2052,7 +2326,8 @@ class Reline::LineEditor
2052
2326
  @cursor = 0
2053
2327
  end
2054
2328
 
2055
- private def vi_change_meta(key)
2329
+ private def vi_change_meta(key, arg: 1)
2330
+ @drop_terminate_spaces = true
2056
2331
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2057
2332
  if byte_pointer_diff > 0
2058
2333
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2064,10 +2339,12 @@ class Reline::LineEditor
2064
2339
  @cursor_max -= cursor_diff.abs
2065
2340
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2066
2341
  @config.editing_mode = :vi_insert
2342
+ @drop_terminate_spaces = false
2067
2343
  }
2344
+ @waiting_operator_vi_arg = arg
2068
2345
  end
2069
2346
 
2070
- private def vi_delete_meta(key)
2347
+ private def vi_delete_meta(key, arg: 1)
2071
2348
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2072
2349
  if byte_pointer_diff > 0
2073
2350
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2079,9 +2356,19 @@ class Reline::LineEditor
2079
2356
  @cursor_max -= cursor_diff.abs
2080
2357
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2081
2358
  }
2359
+ @waiting_operator_vi_arg = arg
2082
2360
  end
2083
2361
 
2084
- private def vi_yank(key)
2362
+ private def vi_yank(key, arg: 1)
2363
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2364
+ if byte_pointer_diff > 0
2365
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2366
+ elsif byte_pointer_diff < 0
2367
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2368
+ end
2369
+ copy_for_vi(cut)
2370
+ }
2371
+ @waiting_operator_vi_arg = arg
2085
2372
  end
2086
2373
 
2087
2374
  private def vi_list_or_eof(key)
@@ -2108,6 +2395,9 @@ class Reline::LineEditor
2108
2395
  width = Reline::Unicode.get_mbchar_width(mbchar)
2109
2396
  @cursor_max -= width
2110
2397
  if @cursor > 0 and @cursor >= @cursor_max
2398
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2399
+ mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2400
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2111
2401
  @byte_pointer -= byte_size
2112
2402
  @cursor -= width
2113
2403
  end
@@ -2204,7 +2494,7 @@ class Reline::LineEditor
2204
2494
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2205
2495
  before = @line.byteslice(0, @byte_pointer)
2206
2496
  remaining_point = @byte_pointer + byte_size
2207
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2497
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2208
2498
  @line = before + k.chr + after
2209
2499
  @cursor_max = calculate_width(@line)
2210
2500
  @waiting_proc = nil
@@ -2215,7 +2505,7 @@ class Reline::LineEditor
2215
2505
  end
2216
2506
  before = @line.byteslice(0, @byte_pointer)
2217
2507
  remaining_point = @byte_pointer + byte_size
2218
- after = @line.byteslice(remaining_point, @line.size - remaining_point)
2508
+ after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2219
2509
  replaced = k.chr * arg
2220
2510
  @line = before + replaced + after
2221
2511
  @byte_pointer += replaced.bytesize
@@ -2226,15 +2516,15 @@ class Reline::LineEditor
2226
2516
  }
2227
2517
  end
2228
2518
 
2229
- private def vi_next_char(key, arg: 1)
2230
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
2519
+ private def vi_next_char(key, arg: 1, inclusive: false)
2520
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2231
2521
  end
2232
2522
 
2233
- private def vi_to_next_char(key, arg: 1)
2234
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
2523
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2524
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2235
2525
  end
2236
2526
 
2237
- private def search_next_char(key, arg, need_prev_char = false)
2527
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2238
2528
  if key.instance_of?(String)
2239
2529
  inputed_char = key
2240
2530
  else
@@ -2271,6 +2561,15 @@ class Reline::LineEditor
2271
2561
  @byte_pointer += byte_size
2272
2562
  @cursor += width
2273
2563
  end
2564
+ if inclusive
2565
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2566
+ if byte_size > 0
2567
+ c = @line.byteslice(@byte_pointer, byte_size)
2568
+ width = Reline::Unicode.get_mbchar_width(c)
2569
+ @byte_pointer += byte_size
2570
+ @cursor += width
2571
+ end
2572
+ end
2274
2573
  @waiting_proc = nil
2275
2574
  end
2276
2575
 
@@ -2342,6 +2641,7 @@ class Reline::LineEditor
2342
2641
  alias_method :set_mark, :em_set_mark
2343
2642
 
2344
2643
  private def em_exchange_mark(key)
2644
+ return unless @mark_pointer
2345
2645
  new_pointer = [@byte_pointer, @line_index]
2346
2646
  @previous_line_index = @line_index
2347
2647
  @byte_pointer, @line_index = @mark_pointer