reline 0.1.4 → 0.1.9

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: 41e0f73e51def110f060454c69c3a3a29d3b0cedce2c432cc594df4f6cccf012
4
- data.tar.gz: c47d69d20e28f8940c86eedba5349892ecf5e5745e83a8c701eab327e326c6ba
3
+ metadata.gz: 981888e54748ace72084309bf4d0b832503970956188b3865629da1c577b5edb
4
+ data.tar.gz: 11815b8b07d66ef247ab83e37d6c8884fdc4745e681b298237e2e32b631c4cbb
5
5
  SHA512:
6
- metadata.gz: b494460955ab34ccf2e4c0443e76598c692d2846162be5081ecc8234181b47771721e5969fa76acd54bcce7f6d2c3dbc7e6469b23897c144c6233db5c08c915b
7
- data.tar.gz: 7c0f5bd0caf7f79113b479e9785aa2260536356ff7196fa1d034fe669f60fca7078771575d8002edf18a5c256105e22d550d9b4e0382071e2c240ef0a34b0287
6
+ metadata.gz: 8b9df7fa6a4e7196773314b8e5287021fbdfcdd81372740c0c32d60c03cc4aed8fda02f5b14e337c428c160b1ff5a2c4144f5a5866af8f57e50fcb25b51b9099
7
+ data.tar.gz: 50920eaeb2ef94d831c4eb4d798e41ccf9c7a826f237b1fc3343969e6af2ea05332cb38e4638c2fa439a29231a3789b0f4d0ee731d3106aaa6f6d08b12cbcb39
@@ -7,6 +7,7 @@ require 'reline/key_actor'
7
7
  require 'reline/key_stroke'
8
8
  require 'reline/line_editor'
9
9
  require 'reline/history'
10
+ require 'rbconfig'
10
11
 
11
12
  module Reline
12
13
  FILENAME_COMPLETION_PROC = nil
@@ -98,22 +99,22 @@ module Reline
98
99
  end
99
100
 
100
101
  def completion_proc=(p)
101
- raise ArgumentError unless p.respond_to?(:call)
102
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
102
103
  @completion_proc = p
103
104
  end
104
105
 
105
106
  def output_modifier_proc=(p)
106
- raise ArgumentError unless p.respond_to?(:call)
107
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
107
108
  @output_modifier_proc = p
108
109
  end
109
110
 
110
111
  def prompt_proc=(p)
111
- raise ArgumentError unless p.respond_to?(:call)
112
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
112
113
  @prompt_proc = p
113
114
  end
114
115
 
115
116
  def auto_indent_proc=(p)
116
- raise ArgumentError unless p.respond_to?(:call)
117
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
117
118
  @auto_indent_proc = p
118
119
  end
119
120
 
@@ -122,7 +123,7 @@ module Reline
122
123
  end
123
124
 
124
125
  def dig_perfect_match_proc=(p)
125
- raise ArgumentError unless p.respond_to?(:call)
126
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
126
127
  @dig_perfect_match_proc = p
127
128
  end
128
129
 
@@ -222,7 +223,6 @@ module Reline
222
223
  line_editor.auto_indent_proc = auto_indent_proc
223
224
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
224
225
  line_editor.pre_input_hook = pre_input_hook
225
- line_editor.rerender
226
226
 
227
227
  unless config.test_mode
228
228
  config.read
@@ -232,17 +232,27 @@ module Reline
232
232
  end
233
233
  end
234
234
 
235
+ line_editor.rerender
236
+
235
237
  begin
238
+ prev_pasting_state = false
236
239
  loop do
240
+ prev_pasting_state = Reline::IOGate.in_pasting?
237
241
  read_io(config.keyseq_timeout) { |inputs|
238
242
  inputs.each { |c|
239
243
  line_editor.input_key(c)
240
244
  line_editor.rerender
241
245
  }
242
246
  }
247
+ if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
248
+ prev_pasting_state = false
249
+ line_editor.rerender_all
250
+ end
243
251
  break if line_editor.finished?
244
252
  end
245
253
  Reline::IOGate.move_cursor_column(0)
254
+ rescue Errno::EIO
255
+ # Maybe the I/O has been closed.
246
256
  rescue StandardError => e
247
257
  line_editor.finalize
248
258
  Reline::IOGate.deprep(otio)
@@ -28,12 +28,22 @@ class Reline::ANSI
28
28
  [27, 71, 67] => :ed_next_char, # →
29
29
  [27, 71, 68] => :ed_prev_char, # ←
30
30
 
31
+ # urxvt / exoterm
32
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
33
+ [27, 91, 56, 126] => :ed_move_to_end, # End
34
+
31
35
  # GNOME
32
36
  [27, 79, 72] => :ed_move_to_beg, # Home
33
37
  [27, 79, 70] => :ed_move_to_end, # End
34
38
  # Del is 0x08
35
39
  # Arrow keys are the same of KDE
36
40
 
41
+ # iTerm2
42
+ [27, 27, 91, 67] => :em_next_word, # Option+→
43
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←
44
+ [195, 166] => :em_next_word, # Option+f
45
+ [195, 162] => :ed_prev_word, # Option+b
46
+
37
47
  # others
38
48
  [27, 32] => :em_set_mark, # M-<space>
39
49
  [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
@@ -61,8 +71,29 @@ class Reline::ANSI
61
71
  unless @@buf.empty?
62
72
  return @@buf.shift
63
73
  end
64
- c = @@input.raw(intr: true, &:getbyte)
74
+ until c = @@input.raw(intr: true, &:getbyte)
75
+ sleep 0.1
76
+ end
65
77
  (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
78
+ rescue Errno::EIO
79
+ # Maybe the I/O has been closed.
80
+ nil
81
+ end
82
+
83
+ def self.in_pasting?
84
+ not Reline::IOGate.empty_buffer?
85
+ end
86
+
87
+ def self.empty_buffer?
88
+ unless @@buf.empty?
89
+ return false
90
+ end
91
+ rs, = IO.select([@@input], [], [], 0.00001)
92
+ if rs and rs[0]
93
+ false
94
+ else
95
+ true
96
+ end
66
97
  end
67
98
 
68
99
  def self.ungetc(c)
@@ -105,10 +136,13 @@ class Reline::ANSI
105
136
  @@input.raw do |stdin|
106
137
  @@output << "\e[6n"
107
138
  @@output.flush
108
- while (c = stdin.getc) != 'R'
109
- res << c if c
139
+ loop do
140
+ c = stdin.getc
141
+ next if c.nil?
142
+ res << c
143
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
144
+ break if m
110
145
  end
111
- m = res.match(/\e\[(?<row>\d+);(?<column>\d+)/)
112
146
  (m.pre_match + m.post_match).chars.reverse_each do |ch|
113
147
  stdin.ungetc ch
114
148
  end
@@ -116,9 +150,16 @@ class Reline::ANSI
116
150
  column = m[:column].to_i - 1
117
151
  row = m[:row].to_i - 1
118
152
  rescue Errno::ENOTTY
119
- buf = @@output.pread(@@output.pos, 0)
120
- row = buf.count("\n")
121
- column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
153
+ begin
154
+ buf = @@output.pread(@@output.pos, 0)
155
+ row = buf.count("\n")
156
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
157
+ rescue Errno::ESPIPE
158
+ # Just returns column 1 for ambiguous width because this I/O is not
159
+ # tty and can't seek.
160
+ row = 0
161
+ column = 1
162
+ end
122
163
  end
123
164
  Reline::CursorPos.new(column, row)
124
165
  end
@@ -1,5 +1,3 @@
1
- require 'pathname'
2
-
3
1
  class Reline::Config
4
2
  attr_reader :test_mode
5
3
 
@@ -35,6 +33,10 @@ class Reline::Config
35
33
  show-all-if-ambiguous
36
34
  show-all-if-unmodified
37
35
  visible-stats
36
+ show-mode-in-prompt
37
+ vi-cmd-mode-icon
38
+ vi-ins-mode-icon
39
+ emacs-mode-string
38
40
  }
39
41
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
40
42
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -52,6 +54,10 @@ class Reline::Config
52
54
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
53
55
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
54
56
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
+ @vi_cmd_mode_icon = '(cmd)'
58
+ @vi_ins_mode_icon = '(ins)'
59
+ @emacs_mode_string = '@'
60
+ # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
55
61
  @history_size = -1 # unlimited
56
62
  @keyseq_timeout = 500
57
63
  @test_mode = false
@@ -159,7 +165,7 @@ class Reline::Config
159
165
 
160
166
  case line
161
167
  when /^set +([^ ]+) +([^ ]+)/i
162
- var, value = $1.downcase, $2.downcase
168
+ var, value = $1.downcase, $2
163
169
  bind_variable(var, value)
164
170
  next
165
171
  when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
@@ -209,7 +215,11 @@ class Reline::Config
209
215
  def bind_variable(name, value)
210
216
  case name
211
217
  when 'history-size'
212
- @history_size = value.to_i
218
+ begin
219
+ @history_size = Integer(value)
220
+ rescue ArgumentError
221
+ @history_size = 500
222
+ end
213
223
  when 'bell-style'
214
224
  @bell_style =
215
225
  case value
@@ -248,12 +258,35 @@ class Reline::Config
248
258
  end
249
259
  when 'keyseq-timeout'
250
260
  @keyseq_timeout = value.to_i
261
+ when 'show-mode-in-prompt'
262
+ case value
263
+ when 'off'
264
+ @show_mode_in_prompt = false
265
+ when 'on'
266
+ @show_mode_in_prompt = true
267
+ else
268
+ @show_mode_in_prompt = false
269
+ end
270
+ when 'vi-cmd-mode-string'
271
+ @vi_cmd_mode_icon = retrieve_string(value)
272
+ when 'vi-ins-mode-string'
273
+ @vi_ins_mode_icon = retrieve_string(value)
274
+ when 'emacs-mode-string'
275
+ @emacs_mode_string = retrieve_string(value)
251
276
  when *VARIABLE_NAMES then
252
277
  variable_name = :"@#{name.tr(?-, ?_)}"
253
278
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
254
279
  end
255
280
  end
256
281
 
282
+ def retrieve_string(str)
283
+ if str =~ /\A"(.*)"\z/
284
+ parse_keyseq($1).map(&:chr).join
285
+ else
286
+ parse_keyseq(str).map(&:chr).join
287
+ end
288
+ end
289
+
257
290
  def bind_key(key, func_name)
258
291
  if key =~ /\A"(.*)"\z/
259
292
  keyseq = parse_keyseq($1)
@@ -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,6 +71,20 @@ class Reline::GeneralIO
67
71
  def self.set_winch_handler(&handler)
68
72
  end
69
73
 
74
+ @@pasting = false
75
+
76
+ def self.in_pasting?
77
+ @@pasting
78
+ end
79
+
80
+ def self.start_pasting
81
+ @@pasting = true
82
+ end
83
+
84
+ def self.finish_pasting
85
+ @@pasting = false
86
+ end
87
+
70
88
  def self.prep
71
89
  end
72
90
 
@@ -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
@@ -42,6 +42,8 @@ class Reline::KeyStroke
42
42
  expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
43
43
  when Symbol
44
44
  [rhs] + expand(input.drop(lhs.size))
45
+ when Array
46
+ rhs
45
47
  end
46
48
  end
47
49
 
@@ -2,7 +2,6 @@ require 'reline/kill_ring'
2
2
  require 'reline/unicode'
3
3
 
4
4
  require 'tempfile'
5
- require 'pathname'
6
5
 
7
6
  class Reline::LineEditor
8
7
  # TODO: undo
@@ -51,18 +50,20 @@ class Reline::LineEditor
51
50
  CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
52
51
  MenuInfo = Struct.new('MenuInfo', :target, :list)
53
52
 
54
- CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
55
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
56
- NON_PRINTING_START = "\1"
57
- NON_PRINTING_END = "\2"
58
- WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
59
-
60
53
  def initialize(config, encoding)
61
54
  @config = config
62
55
  @completion_append_character = ''
63
56
  reset_variables(encoding: encoding)
64
57
  end
65
58
 
59
+ def simplified_rendering?
60
+ if finished?
61
+ false
62
+ else
63
+ not @rerender_all and not finished? and Reline::IOGate.in_pasting?
64
+ end
65
+ end
66
+
66
67
  private def check_multiline_prompt(buffer, prompt)
67
68
  if @vi_arg
68
69
  prompt = "(arg: #{@vi_arg}) "
@@ -73,14 +74,39 @@ class Reline::LineEditor
73
74
  else
74
75
  prompt = @prompt
75
76
  end
77
+ return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
76
78
  if @prompt_proc
77
79
  prompt_list = @prompt_proc.(buffer)
78
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 = '?'
90
+ end
91
+ prompt_list.map!{ |pr| mode_icon + pr }
92
+ end
79
93
  prompt = prompt_list[@line_index]
80
94
  prompt_width = calculate_width(prompt, true)
81
95
  [prompt, prompt_width, prompt_list]
82
96
  else
83
97
  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
84
110
  [prompt, prompt_width, nil]
85
111
  end
86
112
  end
@@ -116,7 +142,7 @@ class Reline::LineEditor
116
142
  if @line_index.zero?
117
143
  0
118
144
  else
119
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
145
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
120
146
  end
121
147
  if @prompt_proc
122
148
  prompt = prompt_list[@line_index]
@@ -153,6 +179,7 @@ class Reline::LineEditor
153
179
  @vi_arg = nil
154
180
  @waiting_proc = nil
155
181
  @waiting_operator_proc = nil
182
+ @waiting_operator_vi_arg = nil
156
183
  @completion_journey_data = nil
157
184
  @completion_state = CompletionState::NORMAL
158
185
  @perfect_matched = nil
@@ -160,7 +187,9 @@ class Reline::LineEditor
160
187
  @first_prompt = true
161
188
  @searching_prompt = nil
162
189
  @first_char = true
190
+ @add_newline_to_end_of_buffer = false
163
191
  @eof = false
192
+ @continuous_insertion_buffer = String.new(encoding: @encoding)
164
193
  reset_line
165
194
  end
166
195
 
@@ -190,10 +219,10 @@ class Reline::LineEditor
190
219
  @is_multiline = false
191
220
  end
192
221
 
193
- private def calculate_height_by_lines(lines, prompt_list)
222
+ private def calculate_height_by_lines(lines, prompt)
194
223
  result = 0
224
+ prompt_list = prompt.is_a?(Array) ? prompt : nil
195
225
  lines.each_with_index { |line, i|
196
- prompt = ''
197
226
  prompt = prompt_list[i] if prompt_list and prompt_list[i]
198
227
  result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
199
228
  }
@@ -211,40 +240,8 @@ class Reline::LineEditor
211
240
  width.div(@screen_size.last) + 1
212
241
  end
213
242
 
214
- private def split_by_width(prompt, str, max_width)
215
- lines = [String.new(encoding: @encoding)]
216
- height = 1
217
- width = 0
218
- rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
219
- in_zero_width = false
220
- rest.scan(WIDTH_SCANNER) do |gc|
221
- case gc
222
- when NON_PRINTING_START
223
- in_zero_width = true
224
- when NON_PRINTING_END
225
- in_zero_width = false
226
- when CSI_REGEXP, OSC_REGEXP
227
- lines.last << gc
228
- else
229
- unless in_zero_width
230
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
231
- if (width += mbchar_width) > max_width
232
- width = mbchar_width
233
- lines << nil
234
- lines << String.new(encoding: @encoding)
235
- height += 1
236
- end
237
- end
238
- lines.last << gc
239
- end
240
- end
241
- # The cursor moves to next line in first
242
- if width == max_width
243
- lines << nil
244
- lines << String.new(encoding: @encoding)
245
- height += 1
246
- end
247
- [lines, height]
243
+ private def split_by_width(str, max_width)
244
+ Reline::Unicode.split_by_width(str, max_width, @encoding)
248
245
  end
249
246
 
250
247
  private def scroll_down(val)
@@ -312,6 +309,11 @@ class Reline::LineEditor
312
309
  @byte_pointer = new_byte_pointer
313
310
  end
314
311
 
312
+ def rerender_all
313
+ @rerender_all = true
314
+ rerender
315
+ end
316
+
315
317
  def rerender
316
318
  return if @line.nil?
317
319
  if @menu_info
@@ -351,14 +353,29 @@ class Reline::LineEditor
351
353
  end
352
354
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
353
355
  # FIXME: end of logical line sometimes breaks
354
- if @previous_line_index or new_highest_in_this != @highest_in_this
356
+ if @add_newline_to_end_of_buffer
357
+ scroll_down(1)
358
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
359
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
360
+ @buffer_of_lines[@previous_line_index] = @line
361
+ @line = @buffer_of_lines[@line_index]
362
+ render_partial(prompt, prompt_width, @line, false)
363
+ @cursor = @cursor_max = calculate_width(@line)
364
+ @byte_pointer = @line.bytesize
365
+ @highest_in_all += @highest_in_this
366
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
367
+ @first_line_started_from += @started_from + 1
368
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
369
+ @previous_line_index = nil
370
+ @add_newline_to_end_of_buffer = false
371
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
355
372
  if @previous_line_index
356
373
  new_lines = whole_lines(index: @previous_line_index, line: @line)
357
374
  else
358
375
  new_lines = whole_lines
359
376
  end
360
377
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
361
- all_height = calculate_height_by_lines(new_lines, prompt_list)
378
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
362
379
  diff = all_height - @highest_in_all
363
380
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
364
381
  if diff > 0
@@ -398,7 +415,7 @@ class Reline::LineEditor
398
415
  if @line_index.zero?
399
416
  0
400
417
  else
401
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
418
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
402
419
  end
403
420
  if @prompt_proc
404
421
  prompt = prompt_list[@line_index]
@@ -457,7 +474,7 @@ class Reline::LineEditor
457
474
  if @line_index.zero?
458
475
  0
459
476
  else
460
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
477
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
461
478
  end
462
479
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
463
480
  move_cursor_down(@first_line_started_from + @started_from)
@@ -488,7 +505,7 @@ class Reline::LineEditor
488
505
  end
489
506
 
490
507
  private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
491
- visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
508
+ visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
492
509
  if with_control
493
510
  if height > @highest_in_this
494
511
  diff = height - @highest_in_this
@@ -507,8 +524,18 @@ class Reline::LineEditor
507
524
  Reline::IOGate.move_cursor_column(0)
508
525
  visual_lines.each_with_index do |line, index|
509
526
  if line.nil?
510
- if Reline::IOGate.win? and calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
511
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
527
+ if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
528
+ # reaches the end of line
529
+ if Reline::IOGate.win?
530
+ # A newline is automatically inserted if a character is rendered at
531
+ # eol on command prompt.
532
+ else
533
+ # When the cursor is at the end of the line and erases characters
534
+ # after the cursor, some terminals delete the character at the
535
+ # cursor position.
536
+ move_cursor_down(1)
537
+ Reline::IOGate.move_cursor_column(0)
538
+ end
512
539
  else
513
540
  Reline::IOGate.erase_after_cursor
514
541
  move_cursor_down(1)
@@ -528,22 +555,25 @@ class Reline::LineEditor
528
555
  end
529
556
  end
530
557
  Reline::IOGate.erase_after_cursor
558
+ Reline::IOGate.move_cursor_column(0)
531
559
  if with_control
532
- move_cursor_up(height - 1)
560
+ # Just after rendring, so the cursor is on the last line.
533
561
  if finished?
534
- move_cursor_down(@started_from)
562
+ Reline::IOGate.move_cursor_column(0)
563
+ else
564
+ # Moves up from bottom of lines to the cursor position.
565
+ move_cursor_up(height - 1 - @started_from)
566
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
535
567
  end
536
- move_cursor_down(@started_from)
537
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
538
568
  end
539
569
  height
540
570
  end
541
571
 
542
572
  private def modify_lines(before)
543
- return before if before.nil? || before.empty?
573
+ return before if before.nil? || before.empty? || simplified_rendering?
544
574
 
545
575
  if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
546
- after.lines("\n", chomp: true)
576
+ after.lines("\n").map { |l| l.chomp('') }
547
577
  else
548
578
  before
549
579
  end
@@ -568,7 +598,7 @@ class Reline::LineEditor
568
598
  else
569
599
  i&.start_with?(target)
570
600
  end
571
- }
601
+ }.uniq
572
602
  if is_menu
573
603
  menu(target, list)
574
604
  return nil
@@ -685,7 +715,8 @@ class Reline::LineEditor
685
715
  if @waiting_operator_proc
686
716
  if VI_MOTIONS.include?(method_symbol)
687
717
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
688
- block.()
718
+ @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
719
+ block.(true)
689
720
  unless @waiting_proc
690
721
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
691
722
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
@@ -693,27 +724,56 @@ class Reline::LineEditor
693
724
  else
694
725
  old_waiting_proc = @waiting_proc
695
726
  old_waiting_operator_proc = @waiting_operator_proc
727
+ current_waiting_operator_proc = @waiting_operator_proc
696
728
  @waiting_proc = proc { |k|
697
729
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
698
730
  old_waiting_proc.(k)
699
731
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
700
732
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
701
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
733
+ current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
702
734
  @waiting_operator_proc = old_waiting_operator_proc
703
735
  }
704
736
  end
705
737
  else
706
738
  # Ignores operator when not motion is given.
707
- block.()
739
+ block.(false)
708
740
  end
709
741
  @waiting_operator_proc = nil
742
+ @waiting_operator_vi_arg = nil
743
+ @vi_arg = nil
710
744
  else
711
- block.()
745
+ block.(false)
712
746
  end
713
747
  end
714
748
 
715
749
  private def argumentable?(method_obj)
716
- method_obj and method_obj.parameters.length != 1
750
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
751
+ end
752
+
753
+ private def inclusive?(method_obj)
754
+ # If a motion method with the keyword argument "inclusive" follows the
755
+ # operator, it must contain the character at the cursor position.
756
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
757
+ end
758
+
759
+ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
760
+ if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
761
+ not_insertion = method_symbol != :ed_insert
762
+ process_insert(force: not_insertion)
763
+ end
764
+ if @vi_arg and argumentable?(method_obj)
765
+ if with_operator and inclusive?(method_obj)
766
+ method_obj.(key, arg: @vi_arg, inclusive: true)
767
+ else
768
+ method_obj.(key, arg: @vi_arg)
769
+ end
770
+ else
771
+ if with_operator and inclusive?(method_obj)
772
+ method_obj.(key, inclusive: true)
773
+ else
774
+ method_obj.(key)
775
+ end
776
+ end
717
777
  end
718
778
 
719
779
  private def process_key(key, method_symbol)
@@ -724,11 +784,11 @@ class Reline::LineEditor
724
784
  end
725
785
  if method_symbol and key.is_a?(Symbol)
726
786
  if @vi_arg and argumentable?(method_obj)
727
- run_for_operators(key, method_symbol) do
728
- method_obj.(key, arg: @vi_arg)
787
+ run_for_operators(key, method_symbol) do |with_operator|
788
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
729
789
  end
730
790
  else
731
- method_obj&.(key)
791
+ wrap_method_call(method_symbol, method_obj, key) if method_obj
732
792
  end
733
793
  @kill_ring.process
734
794
  @vi_arg = nil
@@ -737,15 +797,15 @@ class Reline::LineEditor
737
797
  ed_argument_digit(key)
738
798
  else
739
799
  if argumentable?(method_obj)
740
- run_for_operators(key, method_symbol) do
741
- method_obj.(key, arg: @vi_arg)
800
+ run_for_operators(key, method_symbol) do |with_operator|
801
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
742
802
  end
743
803
  elsif @waiting_proc
744
804
  @waiting_proc.(key)
745
805
  elsif method_obj
746
- method_obj.(key)
806
+ wrap_method_call(method_symbol, method_obj, key)
747
807
  else
748
- ed_insert(key)
808
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
749
809
  end
750
810
  @kill_ring.process
751
811
  @vi_arg = nil
@@ -755,15 +815,15 @@ class Reline::LineEditor
755
815
  @kill_ring.process
756
816
  elsif method_obj
757
817
  if method_symbol == :ed_argument_digit
758
- method_obj.(key)
818
+ wrap_method_call(method_symbol, method_obj, key)
759
819
  else
760
- run_for_operators(key, method_symbol) do
761
- method_obj.(key)
820
+ run_for_operators(key, method_symbol) do |with_operator|
821
+ wrap_method_call(method_symbol, method_obj, key, with_operator)
762
822
  end
763
823
  end
764
824
  @kill_ring.process
765
825
  else
766
- ed_insert(key)
826
+ ed_insert(key) unless @config.editing_mode_is?(:vi_command)
767
827
  end
768
828
  end
769
829
 
@@ -820,6 +880,7 @@ class Reline::LineEditor
820
880
  result = call_completion_proc
821
881
  if result.is_a?(Array)
822
882
  completion_occurs = true
883
+ process_insert
823
884
  complete(result)
824
885
  end
825
886
  end
@@ -828,6 +889,7 @@ class Reline::LineEditor
828
889
  result = call_completion_proc
829
890
  if result.is_a?(Array)
830
891
  completion_occurs = true
892
+ process_insert
831
893
  move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
832
894
  end
833
895
  end
@@ -839,7 +901,7 @@ class Reline::LineEditor
839
901
  unless completion_occurs
840
902
  @completion_state = CompletionState::NORMAL
841
903
  end
842
- if @is_multiline and @auto_indent_proc
904
+ if @is_multiline and @auto_indent_proc and not simplified_rendering?
843
905
  process_auto_indent
844
906
  end
845
907
  end
@@ -1041,6 +1103,7 @@ class Reline::LineEditor
1041
1103
 
1042
1104
  def finish
1043
1105
  @finished = true
1106
+ @rerender_all = true
1044
1107
  @config.reset
1045
1108
  end
1046
1109
 
@@ -1058,29 +1121,7 @@ class Reline::LineEditor
1058
1121
  end
1059
1122
 
1060
1123
  private def calculate_width(str, allow_escape_code = false)
1061
- if allow_escape_code
1062
- width = 0
1063
- rest = str.encode(Encoding::UTF_8)
1064
- in_zero_width = false
1065
- rest.scan(WIDTH_SCANNER) do |gc|
1066
- case gc
1067
- when NON_PRINTING_START
1068
- in_zero_width = true
1069
- when NON_PRINTING_END
1070
- in_zero_width = false
1071
- when CSI_REGEXP, OSC_REGEXP
1072
- else
1073
- unless in_zero_width
1074
- width += Reline::Unicode.get_mbchar_width(gc)
1075
- end
1076
- end
1077
- end
1078
- width
1079
- else
1080
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1081
- w + Reline::Unicode.get_mbchar_width(gc)
1082
- }
1083
- end
1124
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1084
1125
  end
1085
1126
 
1086
1127
  private def key_delete(key)
@@ -1091,6 +1132,9 @@ class Reline::LineEditor
1091
1132
 
1092
1133
  private def key_newline(key)
1093
1134
  if @is_multiline
1135
+ if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1136
+ @add_newline_to_end_of_buffer = true
1137
+ end
1094
1138
  next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1095
1139
  cursor_line = @line.byteslice(0, @byte_pointer)
1096
1140
  insert_new_line(cursor_line, next_line)
@@ -1101,38 +1145,57 @@ class Reline::LineEditor
1101
1145
 
1102
1146
  private def ed_unassigned(key) end # do nothing
1103
1147
 
1148
+ private def process_insert(force: false)
1149
+ return if @continuous_insertion_buffer.empty? or (Reline::IOGate.in_pasting? and not force)
1150
+ width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1151
+ bytesize = @continuous_insertion_buffer.bytesize
1152
+ if @cursor == @cursor_max
1153
+ @line += @continuous_insertion_buffer
1154
+ else
1155
+ @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1156
+ end
1157
+ @byte_pointer += bytesize
1158
+ @cursor += width
1159
+ @cursor_max += width
1160
+ @continuous_insertion_buffer.clear
1161
+ end
1162
+
1104
1163
  private def ed_insert(key)
1164
+ str = nil
1165
+ width = nil
1166
+ bytesize = nil
1105
1167
  if key.instance_of?(String)
1106
1168
  begin
1107
1169
  key.encode(Encoding::UTF_8)
1108
1170
  rescue Encoding::UndefinedConversionError
1109
1171
  return
1110
1172
  end
1111
- width = Reline::Unicode.get_mbchar_width(key)
1112
- if @cursor == @cursor_max
1113
- @line += key
1114
- else
1115
- @line = byteinsert(@line, @byte_pointer, key)
1116
- end
1117
- @byte_pointer += key.bytesize
1118
- @cursor += width
1119
- @cursor_max += width
1173
+ str = key
1174
+ bytesize = key.bytesize
1120
1175
  else
1121
1176
  begin
1122
1177
  key.chr.encode(Encoding::UTF_8)
1123
1178
  rescue Encoding::UndefinedConversionError
1124
1179
  return
1125
1180
  end
1126
- if @cursor == @cursor_max
1127
- @line += key.chr
1128
- else
1129
- @line = byteinsert(@line, @byte_pointer, key.chr)
1130
- end
1131
- width = Reline::Unicode.get_mbchar_width(key.chr)
1132
- @byte_pointer += 1
1133
- @cursor += width
1134
- @cursor_max += width
1181
+ str = key.chr
1182
+ bytesize = 1
1183
+ end
1184
+ if Reline::IOGate.in_pasting?
1185
+ @continuous_insertion_buffer << str
1186
+ return
1187
+ elsif not @continuous_insertion_buffer.empty?
1188
+ process_insert
1135
1189
  end
1190
+ width = Reline::Unicode.get_mbchar_width(str)
1191
+ if @cursor == @cursor_max
1192
+ @line += str
1193
+ else
1194
+ @line = byteinsert(@line, @byte_pointer, str)
1195
+ end
1196
+ @byte_pointer += bytesize
1197
+ @cursor += width
1198
+ @cursor_max += width
1136
1199
  end
1137
1200
  alias_method :ed_digit, :ed_insert
1138
1201
  alias_method :self_insert, :ed_insert
@@ -1189,6 +1252,7 @@ class Reline::LineEditor
1189
1252
  arg -= 1
1190
1253
  ed_prev_char(key, arg: arg) if arg > 0
1191
1254
  end
1255
+ alias_method :backward_char, :ed_prev_char
1192
1256
 
1193
1257
  private def vi_first_print(key)
1194
1258
  @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
@@ -1258,7 +1322,7 @@ class Reline::LineEditor
1258
1322
  if search_word.empty? and Reline.last_incremental_search
1259
1323
  search_word = Reline.last_incremental_search
1260
1324
  end
1261
- if @history_pointer # TODO
1325
+ if @history_pointer
1262
1326
  case prev_search_key
1263
1327
  when "\C-r".ord
1264
1328
  history_pointer_base = 0
@@ -1618,6 +1682,7 @@ class Reline::LineEditor
1618
1682
  end
1619
1683
 
1620
1684
  private def ed_newline(key)
1685
+ process_insert(force: true)
1621
1686
  if @is_multiline
1622
1687
  if @config.editing_mode_is?(:vi_command)
1623
1688
  if @line_index < (@buffer_of_lines.size - 1)
@@ -1915,7 +1980,7 @@ class Reline::LineEditor
1915
1980
  ed_prev_char(key)
1916
1981
  @config.editing_mode = :vi_command
1917
1982
  end
1918
- alias_method :backward_char, :ed_prev_char
1983
+ alias_method :vi_movement_mode, :vi_command_mode
1919
1984
 
1920
1985
  private def vi_next_word(key, arg: 1)
1921
1986
  if @line.bytesize > @byte_pointer
@@ -1937,13 +2002,22 @@ class Reline::LineEditor
1937
2002
  vi_prev_word(key, arg: arg) if arg > 0
1938
2003
  end
1939
2004
 
1940
- private def vi_end_word(key, arg: 1)
2005
+ private def vi_end_word(key, arg: 1, inclusive: false)
1941
2006
  if @line.bytesize > @byte_pointer
1942
2007
  byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
1943
2008
  @byte_pointer += byte_size
1944
2009
  @cursor += width
1945
2010
  end
1946
2011
  arg -= 1
2012
+ if inclusive and arg.zero?
2013
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2014
+ if byte_size > 0
2015
+ c = @line.byteslice(@byte_pointer, byte_size)
2016
+ width = Reline::Unicode.get_mbchar_width(c)
2017
+ @byte_pointer += byte_size
2018
+ @cursor += width
2019
+ end
2020
+ end
1947
2021
  vi_end_word(key, arg: arg) if arg > 0
1948
2022
  end
1949
2023
 
@@ -1967,13 +2041,22 @@ class Reline::LineEditor
1967
2041
  vi_prev_big_word(key, arg: arg) if arg > 0
1968
2042
  end
1969
2043
 
1970
- private def vi_end_big_word(key, arg: 1)
2044
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1971
2045
  if @line.bytesize > @byte_pointer
1972
2046
  byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
1973
2047
  @byte_pointer += byte_size
1974
2048
  @cursor += width
1975
2049
  end
1976
2050
  arg -= 1
2051
+ if inclusive and arg.zero?
2052
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2053
+ if byte_size > 0
2054
+ c = @line.byteslice(@byte_pointer, byte_size)
2055
+ width = Reline::Unicode.get_mbchar_width(c)
2056
+ @byte_pointer += byte_size
2057
+ @cursor += width
2058
+ end
2059
+ end
1977
2060
  vi_end_big_word(key, arg: arg) if arg > 0
1978
2061
  end
1979
2062
 
@@ -2028,7 +2111,7 @@ class Reline::LineEditor
2028
2111
  @cursor = 0
2029
2112
  end
2030
2113
 
2031
- private def vi_change_meta(key)
2114
+ private def vi_change_meta(key, arg: 1)
2032
2115
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2033
2116
  if byte_pointer_diff > 0
2034
2117
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2041,9 +2124,10 @@ class Reline::LineEditor
2041
2124
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2042
2125
  @config.editing_mode = :vi_insert
2043
2126
  }
2127
+ @waiting_operator_vi_arg = arg
2044
2128
  end
2045
2129
 
2046
- private def vi_delete_meta(key)
2130
+ private def vi_delete_meta(key, arg: 1)
2047
2131
  @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2048
2132
  if byte_pointer_diff > 0
2049
2133
  @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
@@ -2055,9 +2139,19 @@ class Reline::LineEditor
2055
2139
  @cursor_max -= cursor_diff.abs
2056
2140
  @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2057
2141
  }
2142
+ @waiting_operator_vi_arg = arg
2058
2143
  end
2059
2144
 
2060
- private def vi_yank(key)
2145
+ private def vi_yank(key, arg: 1)
2146
+ @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2147
+ if byte_pointer_diff > 0
2148
+ cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2149
+ elsif byte_pointer_diff < 0
2150
+ cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2151
+ end
2152
+ copy_for_vi(cut)
2153
+ }
2154
+ @waiting_operator_vi_arg = arg
2061
2155
  end
2062
2156
 
2063
2157
  private def vi_list_or_eof(key)
@@ -2121,7 +2215,7 @@ class Reline::LineEditor
2121
2215
  fp.path
2122
2216
  }
2123
2217
  system("#{ENV['EDITOR']} #{path}")
2124
- @line = Pathname.new(path).read
2218
+ @line = File.read(path)
2125
2219
  finish
2126
2220
  end
2127
2221
 
@@ -2202,15 +2296,15 @@ class Reline::LineEditor
2202
2296
  }
2203
2297
  end
2204
2298
 
2205
- private def vi_next_char(key, arg: 1)
2206
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
2299
+ private def vi_next_char(key, arg: 1, inclusive: false)
2300
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2207
2301
  end
2208
2302
 
2209
- private def vi_to_next_char(key, arg: 1)
2210
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
2303
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2304
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2211
2305
  end
2212
2306
 
2213
- private def search_next_char(key, arg, need_prev_char = false)
2307
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2214
2308
  if key.instance_of?(String)
2215
2309
  inputed_char = key
2216
2310
  else
@@ -2247,6 +2341,15 @@ class Reline::LineEditor
2247
2341
  @byte_pointer += byte_size
2248
2342
  @cursor += width
2249
2343
  end
2344
+ if inclusive
2345
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2346
+ if byte_size > 0
2347
+ c = @line.byteslice(@byte_pointer, byte_size)
2348
+ width = Reline::Unicode.get_mbchar_width(c)
2349
+ @byte_pointer += byte_size
2350
+ @cursor += width
2351
+ end
2352
+ end
2250
2353
  @waiting_proc = nil
2251
2354
  end
2252
2355
 
@@ -2318,10 +2421,10 @@ class Reline::LineEditor
2318
2421
  alias_method :set_mark, :em_set_mark
2319
2422
 
2320
2423
  private def em_exchange_mark(key)
2424
+ return unless @mark_pointer
2321
2425
  new_pointer = [@byte_pointer, @line_index]
2322
2426
  @previous_line_index = @line_index
2323
2427
  @byte_pointer, @line_index = @mark_pointer
2324
- @byte_pointer, @line_index = @mark_pointer
2325
2428
  @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2326
2429
  @cursor_max = calculate_width(@line)
2327
2430
  @mark_pointer = new_pointer