reline 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f165aeb5368335d41b08932f34d0457d612fe4dbe334748964c2bafaaa518b54
4
- data.tar.gz: 4e268dfea3a18f61b6f0bdbbf4d7dd292b3f2d62e178835c971f7b9d3da96613
3
+ metadata.gz: 1da0f8ec80f16c720589413fbc14c02ac199dcfc0b0344270a61edbad4aec966
4
+ data.tar.gz: d214e02ad3500323e98d97d57677b7bbc94ea4c09df0b529d5b21ea66374a6a8
5
5
  SHA512:
6
- metadata.gz: 7c56912f662f9c0e53d793ff108ad44fa2ef231b07fa579ab11e793639b27dd076b6dcb54c7dcc1f68c52a73fbabddf8f41a9d8ea8ea53c06cee481628ea9e18
7
- data.tar.gz: acc258dcbe8b5a3dbe536cc3084906bae2006972f7a3e34353f7f87bbb9b78d692e68b479abb412aae18c396efa2f8dc0210fabc04451cce3f48e7ac8a572093
6
+ metadata.gz: 0d218a92032ad8b02218e44bda7fe7d7404845f6cc9cceb9aade01d3c49bc33a0396b02857de417ecee59b5d65c0cb787eecec8dea60180ae3b5457df6c89ebd
7
+ data.tar.gz: 12c4b8d00608e1f9f16a1c5e530ae86c7530f39ddb1c0f6100b1d4ae87a6dbd53def15d9f387941062aafdb57360fd499dc5850a5bd60d07776daf60e057acfe
data/lib/reline.rb CHANGED
@@ -446,6 +446,10 @@ module Reline
446
446
  }
447
447
  end
448
448
 
449
+ def self.ungetc(c)
450
+ Reline::IOGate.ungetc(c)
451
+ end
452
+
449
453
  def self.line_editor
450
454
  core.line_editor
451
455
  end
@@ -813,6 +813,7 @@ class Reline::LineEditor
813
813
  end
814
814
  move_cursor_up(back)
815
815
  move_cursor_down(@first_line_started_from + @started_from)
816
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
816
817
  Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
817
818
  end
818
819
 
@@ -1158,8 +1159,25 @@ class Reline::LineEditor
1158
1159
 
1159
1160
  def call_completion_proc
1160
1161
  result = retrieve_completion_block(true)
1161
- slice = result[1]
1162
- result = @completion_proc.(slice) if @completion_proc and slice
1162
+ preposing, target, postposing = result
1163
+ if @completion_proc and target
1164
+ argnum = @completion_proc.parameters.inject(0) { |result, item|
1165
+ case item.first
1166
+ when :req, :opt
1167
+ result + 1
1168
+ when :rest
1169
+ break 3
1170
+ end
1171
+ }
1172
+ case argnum
1173
+ when 1
1174
+ result = @completion_proc.(target)
1175
+ when 2
1176
+ result = @completion_proc.(target, preposing)
1177
+ when 3..Float::INFINITY
1178
+ result = @completion_proc.(target, preposing, postposing)
1179
+ end
1180
+ end
1163
1181
  Reline.core.instance_variable_set(:@completion_quote_character, nil)
1164
1182
  result
1165
1183
  end
@@ -1207,8 +1225,16 @@ class Reline::LineEditor
1207
1225
  end
1208
1226
 
1209
1227
  def retrieve_completion_block(set_completion_quote_character = false)
1210
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1211
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1228
+ if Reline.completer_word_break_characters.empty?
1229
+ word_break_regexp = nil
1230
+ else
1231
+ word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1232
+ end
1233
+ if Reline.completer_quote_characters.empty?
1234
+ quote_characters_regexp = nil
1235
+ else
1236
+ quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1237
+ end
1212
1238
  before = @line.byteslice(0, @byte_pointer)
1213
1239
  rest = nil
1214
1240
  break_pointer = nil
@@ -1229,14 +1255,14 @@ class Reline::LineEditor
1229
1255
  elsif quote and slice.start_with?(escaped_quote)
1230
1256
  # skip
1231
1257
  i += 2
1232
- elsif slice =~ quote_characters_regexp # find new "
1258
+ elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
1233
1259
  rest = $'
1234
1260
  quote = $&
1235
1261
  closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1236
1262
  escaped_quote = /\\#{Regexp.escape(quote)}/
1237
1263
  i += 1
1238
1264
  break_pointer = i - 1
1239
- elsif not quote and slice =~ word_break_regexp
1265
+ elsif word_break_regexp and not quote and slice =~ word_break_regexp
1240
1266
  rest = $'
1241
1267
  i += 1
1242
1268
  before = @line.byteslice(i, @byte_pointer - i)
@@ -1264,6 +1290,19 @@ class Reline::LineEditor
1264
1290
  end
1265
1291
  target = before
1266
1292
  end
1293
+ if @is_multiline
1294
+ if @previous_line_index
1295
+ lines = whole_lines(index: @previous_line_index, line: @line)
1296
+ else
1297
+ lines = whole_lines
1298
+ end
1299
+ if @line_index > 0
1300
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1301
+ end
1302
+ if (lines.size - 1) > @line_index
1303
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1304
+ end
1305
+ end
1267
1306
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1268
1307
  end
1269
1308
 
@@ -1291,10 +1330,32 @@ class Reline::LineEditor
1291
1330
 
1292
1331
  def delete_text(start = nil, length = nil)
1293
1332
  if start.nil? and length.nil?
1294
- @line&.clear
1295
- @byte_pointer = 0
1296
- @cursor = 0
1297
- @cursor_max = 0
1333
+ if @is_multiline
1334
+ if @buffer_of_lines.size == 1
1335
+ @line&.clear
1336
+ @byte_pointer = 0
1337
+ @cursor = 0
1338
+ @cursor_max = 0
1339
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1340
+ @buffer_of_lines.pop
1341
+ @line_index -= 1
1342
+ @line = @buffer_of_lines[@line_index]
1343
+ @byte_pointer = 0
1344
+ @cursor = 0
1345
+ @cursor_max = calculate_width(@line)
1346
+ elsif @line_index < (@buffer_of_lines.size - 1)
1347
+ @buffer_of_lines.delete_at(@line_index)
1348
+ @line = @buffer_of_lines[@line_index]
1349
+ @byte_pointer = 0
1350
+ @cursor = 0
1351
+ @cursor_max = calculate_width(@line)
1352
+ end
1353
+ else
1354
+ @line&.clear
1355
+ @byte_pointer = 0
1356
+ @cursor = 0
1357
+ @cursor_max = 0
1358
+ end
1298
1359
  elsif not start.nil? and not length.nil?
1299
1360
  if @line
1300
1361
  before = @line.byteslice(0, start)
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.4'
2
+ VERSION = '0.2.5'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-25 00:00:00.000000000 Z
11
+ date: 2021-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -103,7 +103,6 @@ files:
103
103
  - lib/reline/key_stroke.rb
104
104
  - lib/reline/kill_ring.rb
105
105
  - lib/reline/line_editor.rb
106
- - lib/reline/line_editor.rb.orig
107
106
  - lib/reline/sibori.rb
108
107
  - lib/reline/unicode.rb
109
108
  - lib/reline/unicode/east_asian_width.rb
@@ -129,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
128
  - !ruby/object:Gem::Version
130
129
  version: '0'
131
130
  requirements: []
132
- rubygems_version: 3.1.4
131
+ rubygems_version: 3.2.0.rc.1
133
132
  signing_key:
134
133
  specification_version: 4
135
134
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.
@@ -1,2696 +0,0 @@
1
- require 'reline/kill_ring'
2
- require 'reline/unicode'
3
-
4
- require 'tempfile'
5
-
6
- class Reline::LineEditor
7
- # TODO: undo
8
- attr_reader :line
9
- attr_reader :byte_pointer
10
- attr_accessor :confirm_multiline_termination_proc
11
- attr_accessor :completion_proc
12
- attr_accessor :completion_append_character
13
- attr_accessor :output_modifier_proc
14
- attr_accessor :prompt_proc
15
- attr_accessor :auto_indent_proc
16
- attr_accessor :pre_input_hook
17
- attr_accessor :dig_perfect_match_proc
18
- attr_writer :output
19
-
20
- VI_MOTIONS = %i{
21
- ed_prev_char
22
- ed_next_char
23
- vi_zero
24
- ed_move_to_beg
25
- ed_move_to_end
26
- vi_to_column
27
- vi_next_char
28
- vi_prev_char
29
- vi_next_word
30
- vi_prev_word
31
- vi_to_next_char
32
- vi_to_prev_char
33
- vi_end_word
34
- vi_next_big_word
35
- vi_prev_big_word
36
- vi_end_big_word
37
- vi_repeat_next_char
38
- vi_repeat_prev_char
39
- }
40
-
41
- module CompletionState
42
- NORMAL = :normal
43
- COMPLETION = :completion
44
- MENU = :menu
45
- JOURNEY = :journey
46
- MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
47
- PERFECT_MATCH = :perfect_match
48
- end
49
-
50
- CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
51
- MenuInfo = Struct.new('MenuInfo', :target, :list)
52
-
53
- PROMPT_LIST_CACHE_TIMEOUT = 0.5
54
-
55
- def initialize(config, encoding)
56
- @config = config
57
- @completion_append_character = ''
58
- reset_variables(encoding: encoding)
59
- end
60
-
61
- def set_pasting_state(in_pasting)
62
- @in_pasting = in_pasting
63
- end
64
-
65
- def simplified_rendering?
66
- if finished?
67
- false
68
- elsif @just_cursor_moving and not @rerender_all
69
- true
70
- else
71
- not @rerender_all and not finished? and @in_pasting
72
- end
73
- end
74
-
75
- private def check_mode_string
76
- mode_string = nil
77
- if @config.show_mode_in_prompt
78
- if @config.editing_mode_is?(:vi_command)
79
- mode_string = @config.vi_cmd_mode_string
80
- elsif @config.editing_mode_is?(:vi_insert)
81
- mode_string = @config.vi_ins_mode_string
82
- elsif @config.editing_mode_is?(:emacs)
83
- mode_string = @config.emacs_mode_string
84
- else
85
- mode_string = '?'
86
- end
87
- end
88
- if mode_string != @prev_mode_string
89
- @rerender_all = true
90
- end
91
- @prev_mode_string = mode_string
92
- mode_string
93
- end
94
-
95
- private def check_multiline_prompt(buffer, prompt)
96
- if @vi_arg
97
- prompt = "(arg: #{@vi_arg}) "
98
- @rerender_all = true
99
- elsif @searching_prompt
100
- prompt = @searching_prompt
101
- @rerender_all = true
102
- else
103
- prompt = @prompt
104
- end
105
- if simplified_rendering?
106
- mode_string = check_mode_string
107
- prompt = mode_string + prompt if mode_string
108
- return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
109
- end
110
- if @prompt_proc
111
- use_cached_prompt_list = false
112
- if @cached_prompt_list
113
- if @just_cursor_moving
114
- use_cached_prompt_list = true
115
- elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
116
- use_cached_prompt_list = true
117
- end
118
- end
119
- use_cached_prompt_list = false if @rerender_all
120
- if use_cached_prompt_list
121
- prompt_list = @cached_prompt_list
122
- else
123
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
124
- @prompt_cache_time = Time.now.to_f
125
- end
126
- prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
127
- mode_string = check_mode_string
128
- prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
129
- prompt = prompt_list[@line_index]
130
- prompt = prompt_list[0] if prompt.nil?
131
- prompt = prompt_list.last if prompt.nil?
132
- if buffer.size > prompt_list.size
133
- (buffer.size - prompt_list.size).times do
134
- prompt_list << prompt_list.last
135
- end
136
- end
137
- prompt_width = calculate_width(prompt, true)
138
- [prompt, prompt_width, prompt_list]
139
- else
140
- mode_string = check_mode_string
141
- prompt = mode_string + prompt if mode_string
142
- prompt_width = calculate_width(prompt, true)
143
- [prompt, prompt_width, nil]
144
- end
145
- end
146
-
147
- def reset(prompt = '', encoding:)
148
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
149
- @screen_size = Reline::IOGate.get_screen_size
150
- @screen_height = @screen_size.first
151
- reset_variables(prompt, encoding: encoding)
152
- @old_trap = Signal.trap('SIGINT') {
153
- if @scroll_partial_screen
154
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
155
- else
156
- move_cursor_down(@highest_in_all - @line_index - 1)
157
- end
158
- Reline::IOGate.move_cursor_column(0)
159
- scroll_down(1)
160
- @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
161
- raise Interrupt
162
- }
163
- Reline::IOGate.set_winch_handler do
164
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
165
- old_screen_size = @screen_size
166
- @screen_size = Reline::IOGate.get_screen_size
167
- @screen_height = @screen_size.first
168
- if old_screen_size.last < @screen_size.last # columns increase
169
- @rerender_all = true
170
- rerender
171
- else
172
- back = 0
173
- new_buffer = whole_lines
174
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
175
- new_buffer.each_with_index do |line, index|
176
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
177
- width = prompt_width + calculate_width(line)
178
- height = calculate_height_by_width(width)
179
- back += height
180
- end
181
- @highest_in_all = back
182
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
183
- @first_line_started_from =
184
- if @line_index.zero?
185
- 0
186
- else
187
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
188
- end
189
- if @prompt_proc
190
- prompt = prompt_list[@line_index]
191
- prompt_width = calculate_width(prompt, true)
192
- end
193
- calculate_nearest_cursor
194
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
195
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
196
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
197
- @rerender_all = true
198
- end
199
- end
200
- end
201
-
202
- def finalize
203
- Signal.trap('SIGINT', @old_trap)
204
- end
205
-
206
- def eof?
207
- @eof
208
- end
209
-
210
- def reset_variables(prompt = '', encoding:)
211
- @prompt = prompt
212
- @mark_pointer = nil
213
- @encoding = encoding
214
- @is_multiline = false
215
- @finished = false
216
- @cleared = false
217
- @rerender_all = false
218
- @history_pointer = nil
219
- @kill_ring ||= Reline::KillRing.new
220
- @vi_clipboard = ''
221
- @vi_arg = nil
222
- @waiting_proc = nil
223
- @waiting_operator_proc = nil
224
- @waiting_operator_vi_arg = nil
225
- @completion_journey_data = nil
226
- @completion_state = CompletionState::NORMAL
227
- @perfect_matched = nil
228
- @menu_info = nil
229
- @first_prompt = true
230
- @searching_prompt = nil
231
- @first_char = true
232
- @add_newline_to_end_of_buffer = false
233
- @just_cursor_moving = nil
234
- @cached_prompt_list = nil
235
- @prompt_cache_time = nil
236
- @eof = false
237
- @continuous_insertion_buffer = String.new(encoding: @encoding)
238
- @scroll_partial_screen = nil
239
- @prev_mode_string = nil
240
- @drop_terminate_spaces = false
241
- @in_pasting = false
242
- @auto_indent_proc = nil
243
- reset_line
244
- end
245
-
246
- def reset_line
247
- @cursor = 0
248
- @cursor_max = 0
249
- @byte_pointer = 0
250
- @buffer_of_lines = [String.new(encoding: @encoding)]
251
- @line_index = 0
252
- @previous_line_index = nil
253
- @line = @buffer_of_lines[0]
254
- @first_line_started_from = 0
255
- @move_up = 0
256
- @started_from = 0
257
- @highest_in_this = 1
258
- @highest_in_all = 1
259
- @line_backup_in_history = nil
260
- @multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
261
- @check_new_auto_indent = false
262
- end
263
-
264
- def multiline_on
265
- @is_multiline = true
266
- end
267
-
268
- def multiline_off
269
- @is_multiline = false
270
- end
271
-
272
- private def calculate_height_by_lines(lines, prompt)
273
- result = 0
274
- prompt_list = prompt.is_a?(Array) ? prompt : nil
275
- lines.each_with_index { |line, i|
276
- prompt = prompt_list[i] if prompt_list and prompt_list[i]
277
- result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
278
- }
279
- result
280
- end
281
-
282
- private def insert_new_line(cursor_line, next_line)
283
- @line = cursor_line
284
- @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
285
- @previous_line_index = @line_index
286
- @line_index += 1
287
- @just_cursor_moving = false
288
- end
289
-
290
- private def calculate_height_by_width(width)
291
- width.div(@screen_size.last) + 1
292
- end
293
-
294
- private def split_by_width(str, max_width)
295
- Reline::Unicode.split_by_width(str, max_width, @encoding)
296
- end
297
-
298
- private def scroll_down(val)
299
- if val <= @rest_height
300
- Reline::IOGate.move_cursor_down(val)
301
- @rest_height -= val
302
- else
303
- Reline::IOGate.move_cursor_down(@rest_height)
304
- Reline::IOGate.scroll_down(val - @rest_height)
305
- @rest_height = 0
306
- end
307
- end
308
-
309
- private def move_cursor_up(val)
310
- if val > 0
311
- Reline::IOGate.move_cursor_up(val)
312
- @rest_height += val
313
- elsif val < 0
314
- move_cursor_down(-val)
315
- end
316
- end
317
-
318
- private def move_cursor_down(val)
319
- if val > 0
320
- Reline::IOGate.move_cursor_down(val)
321
- @rest_height -= val
322
- @rest_height = 0 if @rest_height < 0
323
- elsif val < 0
324
- move_cursor_up(-val)
325
- end
326
- end
327
-
328
- private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
329
- new_cursor_max = calculate_width(line_to_calc)
330
- new_cursor = 0
331
- new_byte_pointer = 0
332
- height = 1
333
- max_width = @screen_size.last
334
- if @config.editing_mode_is?(:vi_command)
335
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
336
- if last_byte_size > 0
337
- last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
338
- last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
339
- end_of_line_cursor = new_cursor_max - last_width
340
- else
341
- end_of_line_cursor = new_cursor_max
342
- end
343
- else
344
- end_of_line_cursor = new_cursor_max
345
- end
346
- line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
347
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
348
- now = new_cursor + mbchar_width
349
- if now > end_of_line_cursor or now > cursor
350
- break
351
- end
352
- new_cursor += mbchar_width
353
- if new_cursor > max_width * height
354
- height += 1
355
- end
356
- new_byte_pointer += gc.bytesize
357
- end
358
- new_started_from = height - 1
359
- if update
360
- @cursor = new_cursor
361
- @cursor_max = new_cursor_max
362
- @started_from = new_started_from
363
- @byte_pointer = new_byte_pointer
364
- else
365
- [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
366
- end
367
- end
368
-
369
- def rerender_all
370
- @rerender_all = true
371
- process_insert(force: true)
372
- rerender
373
- end
374
-
375
- def rerender
376
- return if @line.nil?
377
- if @menu_info
378
- scroll_down(@highest_in_all - @first_line_started_from)
379
- @rerender_all = true
380
- end
381
- if @menu_info
382
- show_menu
383
- @menu_info = nil
384
- end
385
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
386
- if @cleared
387
- clear_screen_buffer(prompt, prompt_list, prompt_width)
388
- @cleared = false
389
- return
390
- end
391
- if @is_multiline and finished? and @scroll_partial_screen
392
- # Re-output all code higher than the screen when finished.
393
- Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
394
- Reline::IOGate.move_cursor_column(0)
395
- @scroll_partial_screen = nil
396
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
397
- modify_lines(whole_lines).each_with_index do |line, index|
398
- @output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
399
- Reline::IOGate.erase_after_cursor
400
- end
401
- @output.flush
402
- return
403
- end
404
- new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
405
- # FIXME: end of logical line sometimes breaks
406
- rendered = false
407
- if @add_newline_to_end_of_buffer
408
- rerender_added_newline(prompt, prompt_width)
409
- @add_newline_to_end_of_buffer = false
410
- else
411
- if @just_cursor_moving and not @rerender_all
412
- rendered = just_move_cursor
413
- @just_cursor_moving = false
414
- return
415
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
416
- rerender_changed_current_line
417
- @previous_line_index = nil
418
- rendered = true
419
- elsif @rerender_all
420
- rerender_all_lines
421
- @rerender_all = false
422
- rendered = true
423
- else
424
- end
425
- end
426
- if @is_multiline
427
- if finished?
428
- # Always rerender on finish because output_modifier_proc may return a different output.
429
- line = modify_lines(whole_lines)[@line_index]
430
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
431
- render_partial(prompt, prompt_width, line, @first_line_started_from)
432
- move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
433
- scroll_down(1)
434
- Reline::IOGate.move_cursor_column(0)
435
- Reline::IOGate.erase_after_cursor
436
- elsif not rendered
437
- unless @in_pasting
438
- line = modify_lines(whole_lines)[@line_index]
439
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
440
- render_partial(prompt, prompt_width, line, @first_line_started_from)
441
- end
442
- end
443
- @buffer_of_lines[@line_index] = @line
444
- @rest_height = 0 if @scroll_partial_screen
445
- else
446
- line = modify_lines(whole_lines)[@line_index]
447
- render_partial(prompt, prompt_width, line, 0)
448
- if finished?
449
- scroll_down(1)
450
- Reline::IOGate.move_cursor_column(0)
451
- Reline::IOGate.erase_after_cursor
452
- end
453
- end
454
- end
455
-
456
- private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
457
- if @screen_height < highest_in_all
458
- old_scroll_partial_screen = @scroll_partial_screen
459
- if cursor_y == 0
460
- @scroll_partial_screen = 0
461
- elsif cursor_y == (highest_in_all - 1)
462
- @scroll_partial_screen = highest_in_all - @screen_height
463
- else
464
- if @scroll_partial_screen
465
- if cursor_y <= @scroll_partial_screen
466
- @scroll_partial_screen = cursor_y
467
- elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
468
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
469
- end
470
- else
471
- if cursor_y > (@screen_height - 1)
472
- @scroll_partial_screen = cursor_y - (@screen_height - 1)
473
- else
474
- @scroll_partial_screen = 0
475
- end
476
- end
477
- end
478
- if @scroll_partial_screen != old_scroll_partial_screen
479
- @rerender_all = true
480
- end
481
- else
482
- if @scroll_partial_screen
483
- @rerender_all = true
484
- end
485
- @scroll_partial_screen = nil
486
- end
487
- end
488
-
489
- private def rerender_added_newline(prompt, prompt_width)
490
- scroll_down(1)
491
- @buffer_of_lines[@previous_line_index] = @line
492
- @line = @buffer_of_lines[@line_index]
493
- unless @in_pasting
494
- render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
495
- end
496
- @cursor = @cursor_max = calculate_width(@line)
497
- @byte_pointer = @line.bytesize
498
- @highest_in_all += @highest_in_this
499
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
500
- @first_line_started_from += @started_from + 1
501
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
502
- @previous_line_index = nil
503
- end
504
-
505
- def just_move_cursor
506
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
507
- move_cursor_up(@started_from)
508
- new_first_line_started_from =
509
- if @line_index.zero?
510
- 0
511
- else
512
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
513
- end
514
- first_line_diff = new_first_line_started_from - @first_line_started_from
515
- 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)
516
- new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
517
- calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
518
- @previous_line_index = nil
519
- if @rerender_all
520
- @line = @buffer_of_lines[@line_index]
521
- rerender_all_lines
522
- @rerender_all = false
523
- true
524
- else
525
- @line = @buffer_of_lines[@line_index]
526
- @first_line_started_from = new_first_line_started_from
527
- @started_from = new_started_from
528
- @cursor = new_cursor
529
- @cursor_max = new_cursor_max
530
- @byte_pointer = new_byte_pointer
531
- move_cursor_down(first_line_diff + @started_from)
532
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
533
- false
534
- end
535
- end
536
-
537
- private def rerender_changed_current_line
538
- if @previous_line_index
539
- new_lines = whole_lines(index: @previous_line_index, line: @line)
540
- else
541
- new_lines = whole_lines
542
- end
543
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
544
- all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
545
- diff = all_height - @highest_in_all
546
- move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
547
- if diff > 0
548
- scroll_down(diff)
549
- move_cursor_up(all_height - 1)
550
- elsif diff < 0
551
- (-diff).times do
552
- Reline::IOGate.move_cursor_column(0)
553
- Reline::IOGate.erase_after_cursor
554
- move_cursor_up(1)
555
- end
556
- move_cursor_up(all_height - 1)
557
- else
558
- move_cursor_up(all_height - 1)
559
- end
560
- @highest_in_all = all_height
561
- back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
562
- move_cursor_up(back)
563
- if @previous_line_index
564
- @buffer_of_lines[@previous_line_index] = @line
565
- @line = @buffer_of_lines[@line_index]
566
- end
567
- @first_line_started_from =
568
- if @line_index.zero?
569
- 0
570
- else
571
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
572
- end
573
- if @prompt_proc
574
- prompt = prompt_list[@line_index]
575
- prompt_width = calculate_width(prompt, true)
576
- end
577
- move_cursor_down(@first_line_started_from)
578
- calculate_nearest_cursor
579
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
580
- move_cursor_down(@started_from)
581
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
582
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
583
- end
584
-
585
- private def rerender_all_lines
586
- move_cursor_up(@first_line_started_from + @started_from)
587
- Reline::IOGate.move_cursor_column(0)
588
- back = 0
589
- new_buffer = whole_lines
590
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
591
- new_buffer.each_with_index do |line, index|
592
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
593
- width = prompt_width + calculate_width(line)
594
- height = calculate_height_by_width(width)
595
- back += height
596
- end
597
- old_highest_in_all = @highest_in_all
598
- if @line_index.zero?
599
- new_first_line_started_from = 0
600
- else
601
- new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
602
- end
603
- new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
604
- calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
605
- if @scroll_partial_screen
606
- move_cursor_up(@first_line_started_from + @started_from)
607
- scroll_down(@screen_height - 1)
608
- move_cursor_up(@screen_height)
609
- Reline::IOGate.move_cursor_column(0)
610
- elsif back > old_highest_in_all
611
- scroll_down(back - 1)
612
- move_cursor_up(back - 1)
613
- elsif back < old_highest_in_all
614
- scroll_down(back)
615
- Reline::IOGate.erase_after_cursor
616
- (old_highest_in_all - back - 1).times do
617
- scroll_down(1)
618
- Reline::IOGate.erase_after_cursor
619
- end
620
- move_cursor_up(old_highest_in_all - 1)
621
- end
622
- render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
623
- if @prompt_proc
624
- prompt = prompt_list[@line_index]
625
- prompt_width = calculate_width(prompt, true)
626
- end
627
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
628
- @highest_in_all = back
629
- @first_line_started_from = new_first_line_started_from
630
- @started_from = new_started_from
631
- if @scroll_partial_screen
632
- Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
633
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
634
- else
635
- move_cursor_down(@first_line_started_from + @started_from - back + 1)
636
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
637
- end
638
- end
639
-
640
- private def render_whole_lines(lines, prompt, prompt_width)
641
- rendered_height = 0
642
- modify_lines(lines).each_with_index do |line, index|
643
- if prompt.is_a?(Array)
644
- line_prompt = prompt[index]
645
- prompt_width = calculate_width(line_prompt, true)
646
- else
647
- line_prompt = prompt
648
- end
649
- height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
650
- if index < (lines.size - 1)
651
- if @scroll_partial_screen
652
- if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
653
- move_cursor_down(1)
654
- end
655
- else
656
- scroll_down(1)
657
- end
658
- rendered_height += height
659
- else
660
- rendered_height += height - 1
661
- end
662
- end
663
- rendered_height
664
- end
665
-
666
- private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
667
- visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
668
- cursor_up_from_last_line = 0
669
- # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
670
- if @scroll_partial_screen
671
- last_visual_line = this_started_from + (height - 1)
672
- last_screen_line = @scroll_partial_screen + (@screen_height - 1)
673
- if (@scroll_partial_screen - this_started_from) >= height
674
- # Render nothing because this line is before the screen.
675
- visual_lines = []
676
- elsif this_started_from > last_screen_line
677
- # Render nothing because this line is after the screen.
678
- visual_lines = []
679
- else
680
- deleted_lines_before_screen = []
681
- if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
682
- # A part of visual lines are before the screen.
683
- deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
684
- deleted_lines_before_screen.compact!
685
- end
686
- if this_started_from <= last_screen_line and last_screen_line < last_visual_line
687
- # A part of visual lines are after the screen.
688
- visual_lines.pop((last_visual_line - last_screen_line) * 2)
689
- end
690
- move_cursor_up(deleted_lines_before_screen.size - @started_from)
691
- cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
692
- end
693
- end
694
- if with_control
695
- if height > @highest_in_this
696
- diff = height - @highest_in_this
697
- scroll_down(diff)
698
- @highest_in_all += diff
699
- @highest_in_this = height
700
- move_cursor_up(diff)
701
- elsif height < @highest_in_this
702
- diff = @highest_in_this - height
703
- @highest_in_all -= diff
704
- @highest_in_this = height
705
- end
706
- move_cursor_up(@started_from)
707
- cursor_up_from_last_line = height - 1 - @started_from
708
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
709
- end
710
- if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
711
- @output.write "\e[0m" # clear character decorations
712
- end
713
- visual_lines.each_with_index do |line, index|
714
- Reline::IOGate.move_cursor_column(0)
715
- if line.nil?
716
- if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
717
- # Reaches the end of line.
718
- #
719
- # When the cursor is at the end of the line and erases characters
720
- # after the cursor, some terminals delete the character at the
721
- # cursor position.
722
- move_cursor_down(1)
723
- Reline::IOGate.move_cursor_column(0)
724
- else
725
- Reline::IOGate.erase_after_cursor
726
- move_cursor_down(1)
727
- Reline::IOGate.move_cursor_column(0)
728
- end
729
- next
730
- end
731
- @output.write line
732
- @output.flush
733
- if @first_prompt
734
- @first_prompt = false
735
- @pre_input_hook&.call
736
- end
737
- end
738
- unless visual_lines.empty?
739
- Reline::IOGate.erase_after_cursor
740
- Reline::IOGate.move_cursor_column(0)
741
- end
742
- if with_control
743
- # Just after rendring, so the cursor is on the last line.
744
- if finished?
745
- Reline::IOGate.move_cursor_column(0)
746
- else
747
- # Moves up from bottom of lines to the cursor position.
748
- move_cursor_up(cursor_up_from_last_line)
749
- # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
750
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
751
- end
752
- end
753
- height
754
- end
755
-
756
- private def modify_lines(before)
757
- return before if before.nil? || before.empty? || simplified_rendering?
758
-
759
- if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
760
- after.lines("\n").map { |l| l.chomp('') }
761
- else
762
- before
763
- end
764
- end
765
-
766
- private def show_menu
767
- scroll_down(@highest_in_all - @first_line_started_from)
768
- @rerender_all = true
769
- @menu_info.list.sort!.each do |item|
770
- Reline::IOGate.move_cursor_column(0)
771
- @output.write item
772
- @output.flush
773
- scroll_down(1)
774
- end
775
- scroll_down(@highest_in_all - 1)
776
- move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
777
- end
778
-
779
- private def clear_screen_buffer(prompt, prompt_list, prompt_width)
780
- Reline::IOGate.clear_screen
781
- back = 0
782
- modify_lines(whole_lines).each_with_index do |line, index|
783
- if @prompt_proc
784
- pr = prompt_list[index]
785
- height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
786
- else
787
- height = render_partial(prompt, prompt_width, line, back, with_control: false)
788
- end
789
- if index < (@buffer_of_lines.size - 1)
790
- move_cursor_down(height)
791
- back += height
792
- end
793
- end
794
- move_cursor_up(back)
795
- move_cursor_down(@first_line_started_from + @started_from)
796
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
797
- end
798
-
799
- def editing_mode
800
- @config.editing_mode
801
- end
802
-
803
- private def menu(target, list)
804
- @menu_info = MenuInfo.new(target, list)
805
- end
806
-
807
- private def complete_internal_proc(list, is_menu)
808
- preposing, target, postposing = retrieve_completion_block
809
- list = list.select { |i|
810
- if i and not Encoding.compatible?(target.encoding, i.encoding)
811
- raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
812
- end
813
- if @config.completion_ignore_case
814
- i&.downcase&.start_with?(target.downcase)
815
- else
816
- i&.start_with?(target)
817
- end
818
- }.uniq
819
- if is_menu
820
- menu(target, list)
821
- return nil
822
- end
823
- completed = list.inject { |memo, item|
824
- begin
825
- memo_mbchars = memo.unicode_normalize.grapheme_clusters
826
- item_mbchars = item.unicode_normalize.grapheme_clusters
827
- rescue Encoding::CompatibilityError
828
- memo_mbchars = memo.grapheme_clusters
829
- item_mbchars = item.grapheme_clusters
830
- end
831
- size = [memo_mbchars.size, item_mbchars.size].min
832
- result = ''
833
- size.times do |i|
834
- if @config.completion_ignore_case
835
- if memo_mbchars[i].casecmp?(item_mbchars[i])
836
- result << memo_mbchars[i]
837
- else
838
- break
839
- end
840
- else
841
- if memo_mbchars[i] == item_mbchars[i]
842
- result << memo_mbchars[i]
843
- else
844
- break
845
- end
846
- end
847
- end
848
- result
849
- }
850
- [target, preposing, completed, postposing]
851
- end
852
-
853
- private def complete(list, just_show_list = false)
854
- case @completion_state
855
- when CompletionState::NORMAL, CompletionState::JOURNEY
856
- @completion_state = CompletionState::COMPLETION
857
- when CompletionState::PERFECT_MATCH
858
- @dig_perfect_match_proc&.(@perfect_matched)
859
- end
860
- if just_show_list
861
- is_menu = true
862
- elsif @completion_state == CompletionState::MENU
863
- is_menu = true
864
- elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
865
- is_menu = true
866
- else
867
- is_menu = false
868
- end
869
- result = complete_internal_proc(list, is_menu)
870
- if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
871
- @completion_state = CompletionState::PERFECT_MATCH
872
- end
873
- return if result.nil?
874
- target, preposing, completed, postposing = result
875
- return if completed.nil?
876
- if target <= completed and (@completion_state == CompletionState::COMPLETION)
877
- if list.include?(completed)
878
- if list.one?
879
- @completion_state = CompletionState::PERFECT_MATCH
880
- else
881
- @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
882
- end
883
- @perfect_matched = completed
884
- else
885
- @completion_state = CompletionState::MENU
886
- end
887
- if not just_show_list and target < completed
888
- @line = preposing + completed + completion_append_character.to_s + postposing
889
- line_to_pointer = preposing + completed + completion_append_character.to_s
890
- @cursor_max = calculate_width(@line)
891
- @cursor = calculate_width(line_to_pointer)
892
- @byte_pointer = line_to_pointer.bytesize
893
- end
894
- end
895
- end
896
-
897
- private def move_completed_list(list, direction)
898
- case @completion_state
899
- when CompletionState::NORMAL, CompletionState::COMPLETION,
900
- CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
901
- @completion_state = CompletionState::JOURNEY
902
- result = retrieve_completion_block
903
- return if result.nil?
904
- preposing, target, postposing = result
905
- @completion_journey_data = CompletionJourneyData.new(
906
- preposing, postposing,
907
- [target] + list.select{ |item| item.start_with?(target) }, 0)
908
- @completion_state = CompletionState::JOURNEY
909
- else
910
- case direction
911
- when :up
912
- @completion_journey_data.pointer -= 1
913
- if @completion_journey_data.pointer < 0
914
- @completion_journey_data.pointer = @completion_journey_data.list.size - 1
915
- end
916
- when :down
917
- @completion_journey_data.pointer += 1
918
- if @completion_journey_data.pointer >= @completion_journey_data.list.size
919
- @completion_journey_data.pointer = 0
920
- end
921
- end
922
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
923
- @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
924
- line_to_pointer = @completion_journey_data.preposing + completed
925
- @cursor_max = calculate_width(@line)
926
- @cursor = calculate_width(line_to_pointer)
927
- @byte_pointer = line_to_pointer.bytesize
928
- end
929
- end
930
-
931
- private def run_for_operators(key, method_symbol, &block)
932
- if @waiting_operator_proc
933
- if VI_MOTIONS.include?(method_symbol)
934
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
935
- @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg > 1
936
- block.(true)
937
- unless @waiting_proc
938
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
939
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
940
- @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
941
- else
942
- old_waiting_proc = @waiting_proc
943
- old_waiting_operator_proc = @waiting_operator_proc
944
- current_waiting_operator_proc = @waiting_operator_proc
945
- @waiting_proc = proc { |k|
946
- old_cursor, old_byte_pointer = @cursor, @byte_pointer
947
- old_waiting_proc.(k)
948
- cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
949
- @cursor, @byte_pointer = old_cursor, old_byte_pointer
950
- current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
951
- @waiting_operator_proc = old_waiting_operator_proc
952
- }
953
- end
954
- else
955
- # Ignores operator when not motion is given.
956
- block.(false)
957
- end
958
- @waiting_operator_proc = nil
959
- @waiting_operator_vi_arg = nil
960
- @vi_arg = nil
961
- else
962
- block.(false)
963
- end
964
- end
965
-
966
- private def argumentable?(method_obj)
967
- method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
968
- end
969
-
970
- private def inclusive?(method_obj)
971
- # If a motion method with the keyword argument "inclusive" follows the
972
- # operator, it must contain the character at the cursor position.
973
- method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
974
- end
975
-
976
- def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
977
- if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
978
- not_insertion = method_symbol != :ed_insert
979
- process_insert(force: not_insertion)
980
- end
981
- if @vi_arg and argumentable?(method_obj)
982
- if with_operator and inclusive?(method_obj)
983
- method_obj.(key, arg: @vi_arg, inclusive: true)
984
- else
985
- method_obj.(key, arg: @vi_arg)
986
- end
987
- else
988
- if with_operator and inclusive?(method_obj)
989
- method_obj.(key, inclusive: true)
990
- else
991
- method_obj.(key)
992
- end
993
- end
994
- end
995
-
996
- private def process_key(key, method_symbol)
997
- if method_symbol and respond_to?(method_symbol, true)
998
- method_obj = method(method_symbol)
999
- else
1000
- method_obj = nil
1001
- end
1002
- if method_symbol and key.is_a?(Symbol)
1003
- if @vi_arg and argumentable?(method_obj)
1004
- run_for_operators(key, method_symbol) do |with_operator|
1005
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1006
- end
1007
- else
1008
- wrap_method_call(method_symbol, method_obj, key) if method_obj
1009
- end
1010
- @kill_ring.process
1011
- @vi_arg = nil
1012
- elsif @vi_arg
1013
- if key.chr =~ /[0-9]/
1014
- ed_argument_digit(key)
1015
- else
1016
- if argumentable?(method_obj)
1017
- run_for_operators(key, method_symbol) do |with_operator|
1018
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1019
- end
1020
- elsif @waiting_proc
1021
- @waiting_proc.(key)
1022
- elsif method_obj
1023
- wrap_method_call(method_symbol, method_obj, key)
1024
- else
1025
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1026
- end
1027
- @kill_ring.process
1028
- @vi_arg = nil
1029
- end
1030
- elsif @waiting_proc
1031
- @waiting_proc.(key)
1032
- @kill_ring.process
1033
- elsif method_obj
1034
- if method_symbol == :ed_argument_digit
1035
- wrap_method_call(method_symbol, method_obj, key)
1036
- else
1037
- run_for_operators(key, method_symbol) do |with_operator|
1038
- wrap_method_call(method_symbol, method_obj, key, with_operator)
1039
- end
1040
- end
1041
- @kill_ring.process
1042
- else
1043
- ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1044
- end
1045
- end
1046
-
1047
- private def normal_char(key)
1048
- method_symbol = method_obj = nil
1049
- if key.combined_char.is_a?(Symbol)
1050
- process_key(key.combined_char, key.combined_char)
1051
- return
1052
- end
1053
- @multibyte_buffer << key.combined_char
1054
- if @multibyte_buffer.size > 1
1055
- if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
1056
- process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1057
- @multibyte_buffer.clear
1058
- else
1059
- # invalid
1060
- return
1061
- end
1062
- else # single byte
1063
- return if key.char >= 128 # maybe, first byte of multi byte
1064
- method_symbol = @config.editing_mode.get_method(key.combined_char)
1065
- if key.with_meta and method_symbol == :ed_unassigned
1066
- # split ESC + key
1067
- method_symbol = @config.editing_mode.get_method("\e".ord)
1068
- process_key("\e".ord, method_symbol)
1069
- method_symbol = @config.editing_mode.get_method(key.char)
1070
- process_key(key.char, method_symbol)
1071
- else
1072
- process_key(key.combined_char, method_symbol)
1073
- end
1074
- @multibyte_buffer.clear
1075
- end
1076
- if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
1077
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1078
- @byte_pointer -= byte_size
1079
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1080
- width = Reline::Unicode.get_mbchar_width(mbchar)
1081
- @cursor -= width
1082
- end
1083
- end
1084
-
1085
- def input_key(key)
1086
- @just_cursor_moving = nil
1087
- if key.char.nil?
1088
- if @first_char
1089
- @line = nil
1090
- end
1091
- finish
1092
- return
1093
- end
1094
- old_line = @line.dup
1095
- @first_char = false
1096
- completion_occurs = false
1097
- if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
1098
- unless @config.disable_completion
1099
- result = call_completion_proc
1100
- if result.is_a?(Array)
1101
- completion_occurs = true
1102
- process_insert
1103
- complete(result)
1104
- end
1105
- end
1106
- elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
1107
- unless @config.disable_completion
1108
- result = call_completion_proc
1109
- if result.is_a?(Array)
1110
- completion_occurs = true
1111
- process_insert
1112
- move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
1113
- end
1114
- end
1115
- elsif Symbol === key.char and respond_to?(key.char, true)
1116
- process_key(key.char, key.char)
1117
- else
1118
- normal_char(key)
1119
- end
1120
- unless completion_occurs
1121
- @completion_state = CompletionState::NORMAL
1122
- end
1123
- if not @in_pasting and @just_cursor_moving.nil?
1124
- if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
1125
- @just_cursor_moving = true
1126
- elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
1127
- @just_cursor_moving = true
1128
- else
1129
- @just_cursor_moving = false
1130
- end
1131
- else
1132
- @just_cursor_moving = false
1133
- end
1134
- if @is_multiline and @auto_indent_proc and not simplified_rendering?
1135
- process_auto_indent
1136
- end
1137
- end
1138
-
1139
- def call_completion_proc
1140
- result = retrieve_completion_block(true)
1141
- slice = result[1]
1142
- result = @completion_proc.(slice) if @completion_proc and slice
1143
- Reline.core.instance_variable_set(:@completion_quote_character, nil)
1144
- result
1145
- end
1146
-
1147
- private def process_auto_indent
1148
- return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
1149
- if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
1150
- # Fix indent of a line when a newline is inserted to the next
1151
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1152
- new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
1153
- md = @line.match(/\A */)
1154
- prev_indent = md[0].count(' ')
1155
- @line = ' ' * new_indent + @line.lstrip
1156
-
1157
- new_indent = nil
1158
- result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
1159
- if result
1160
- new_indent = result
1161
- end
1162
- if new_indent&.>= 0
1163
- @line = ' ' * new_indent + @line.lstrip
1164
- end
1165
- end
1166
- if @previous_line_index
1167
- new_lines = whole_lines(index: @previous_line_index, line: @line)
1168
- else
1169
- new_lines = whole_lines
1170
- end
1171
- new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
1172
- new_indent = @cursor_max if new_indent&.> @cursor_max
1173
- if new_indent&.>= 0
1174
- md = new_lines[@line_index].match(/\A */)
1175
- prev_indent = md[0].count(' ')
1176
- if @check_new_auto_indent
1177
- @buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
1178
- @cursor = new_indent
1179
- @byte_pointer = new_indent
1180
- else
1181
- @line = ' ' * new_indent + @line.lstrip
1182
- @cursor += new_indent - prev_indent
1183
- @byte_pointer += new_indent - prev_indent
1184
- end
1185
- end
1186
- @check_new_auto_indent = false
1187
- end
1188
-
1189
- def retrieve_completion_block(set_completion_quote_character = false)
1190
- word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
1191
- quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
1192
- before = @line.byteslice(0, @byte_pointer)
1193
- rest = nil
1194
- break_pointer = nil
1195
- quote = nil
1196
- closing_quote = nil
1197
- escaped_quote = nil
1198
- i = 0
1199
- while i < @byte_pointer do
1200
- slice = @line.byteslice(i, @byte_pointer - i)
1201
- unless slice.valid_encoding?
1202
- i += 1
1203
- next
1204
- end
1205
- if quote and slice.start_with?(closing_quote)
1206
- quote = nil
1207
- i += 1
1208
- rest = nil
1209
- elsif quote and slice.start_with?(escaped_quote)
1210
- # skip
1211
- i += 2
1212
- elsif slice =~ quote_characters_regexp # find new "
1213
- rest = $'
1214
- quote = $&
1215
- closing_quote = /(?!\\)#{Regexp.escape(quote)}/
1216
- escaped_quote = /\\#{Regexp.escape(quote)}/
1217
- i += 1
1218
- break_pointer = i - 1
1219
- elsif not quote and slice =~ word_break_regexp
1220
- rest = $'
1221
- i += 1
1222
- before = @line.byteslice(i, @byte_pointer - i)
1223
- break_pointer = i
1224
- else
1225
- i += 1
1226
- end
1227
- end
1228
- postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1229
- if rest
1230
- preposing = @line.byteslice(0, break_pointer)
1231
- target = rest
1232
- if set_completion_quote_character and quote
1233
- Reline.core.instance_variable_set(:@completion_quote_character, quote)
1234
- if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
1235
- insert_text(quote)
1236
- end
1237
- end
1238
- else
1239
- preposing = ''
1240
- if break_pointer
1241
- preposing = @line.byteslice(0, break_pointer)
1242
- else
1243
- preposing = ''
1244
- end
1245
- target = before
1246
- end
1247
- [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1248
- end
1249
-
1250
- def confirm_multiline_termination
1251
- temp_buffer = @buffer_of_lines.dup
1252
- if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
1253
- temp_buffer[@previous_line_index] = @line
1254
- else
1255
- temp_buffer[@line_index] = @line
1256
- end
1257
- @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1258
- end
1259
-
1260
- def insert_text(text)
1261
- width = calculate_width(text)
1262
- if @cursor == @cursor_max
1263
- @line += text
1264
- else
1265
- @line = byteinsert(@line, @byte_pointer, text)
1266
- end
1267
- @byte_pointer += text.bytesize
1268
- @cursor += width
1269
- @cursor_max += width
1270
- end
1271
-
1272
- def delete_text(start = nil, length = nil)
1273
- if start.nil? and length.nil?
1274
- @line&.clear
1275
- @byte_pointer = 0
1276
- @cursor = 0
1277
- @cursor_max = 0
1278
- elsif not start.nil? and not length.nil?
1279
- if @line
1280
- before = @line.byteslice(0, start)
1281
- after = @line.byteslice(start + length, @line.bytesize)
1282
- @line = before + after
1283
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1284
- str = @line.byteslice(0, @byte_pointer)
1285
- @cursor = calculate_width(str)
1286
- @cursor_max = calculate_width(@line)
1287
- end
1288
- elsif start.is_a?(Range)
1289
- range = start
1290
- first = range.first
1291
- last = range.last
1292
- last = @line.bytesize - 1 if last > @line.bytesize
1293
- last += @line.bytesize if last < 0
1294
- first += @line.bytesize if first < 0
1295
- range = range.exclude_end? ? first...last : first..last
1296
- @line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
1297
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1298
- str = @line.byteslice(0, @byte_pointer)
1299
- @cursor = calculate_width(str)
1300
- @cursor_max = calculate_width(@line)
1301
- else
1302
- @line = @line.byteslice(0, start)
1303
- @byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
1304
- str = @line.byteslice(0, @byte_pointer)
1305
- @cursor = calculate_width(str)
1306
- @cursor_max = calculate_width(@line)
1307
- end
1308
- end
1309
-
1310
- def byte_pointer=(val)
1311
- @byte_pointer = val
1312
- str = @line.byteslice(0, @byte_pointer)
1313
- @cursor = calculate_width(str)
1314
- @cursor_max = calculate_width(@line)
1315
- end
1316
-
1317
- def whole_lines(index: @line_index, line: @line)
1318
- temp_lines = @buffer_of_lines.dup
1319
- temp_lines[index] = line
1320
- temp_lines
1321
- end
1322
-
1323
- def whole_buffer
1324
- if @buffer_of_lines.size == 1 and @line.nil?
1325
- nil
1326
- else
1327
- whole_lines.join("\n")
1328
- end
1329
- end
1330
-
1331
- def finished?
1332
- @finished
1333
- end
1334
-
1335
- def finish
1336
- @finished = true
1337
- @rerender_all = true
1338
- @config.reset
1339
- end
1340
-
1341
- private def byteslice!(str, byte_pointer, size)
1342
- new_str = str.byteslice(0, byte_pointer)
1343
- new_str << str.byteslice(byte_pointer + size, str.bytesize)
1344
- [new_str, str.byteslice(byte_pointer, size)]
1345
- end
1346
-
1347
- private def byteinsert(str, byte_pointer, other)
1348
- new_str = str.byteslice(0, byte_pointer)
1349
- new_str << other
1350
- new_str << str.byteslice(byte_pointer, str.bytesize)
1351
- new_str
1352
- end
1353
-
1354
- private def calculate_width(str, allow_escape_code = false)
1355
- Reline::Unicode.calculate_width(str, allow_escape_code)
1356
- end
1357
-
1358
- private def key_delete(key)
1359
- if @config.editing_mode_is?(:vi_insert, :emacs)
1360
- ed_delete_next_char(key)
1361
- end
1362
- end
1363
-
1364
- private def key_newline(key)
1365
- if @is_multiline
1366
- if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
1367
- @add_newline_to_end_of_buffer = true
1368
- end
1369
- next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
1370
- cursor_line = @line.byteslice(0, @byte_pointer)
1371
- insert_new_line(cursor_line, next_line)
1372
- @cursor = 0
1373
- @check_new_auto_indent = true unless @in_pasting
1374
- end
1375
- end
1376
-
1377
- private def ed_unassigned(key) end # do nothing
1378
-
1379
- private def process_insert(force: false)
1380
- return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1381
- width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
1382
- bytesize = @continuous_insertion_buffer.bytesize
1383
- if @cursor == @cursor_max
1384
- @line += @continuous_insertion_buffer
1385
- else
1386
- @line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
1387
- end
1388
- @byte_pointer += bytesize
1389
- @cursor += width
1390
- @cursor_max += width
1391
- @continuous_insertion_buffer.clear
1392
- end
1393
-
1394
- private def ed_insert(key)
1395
- str = nil
1396
- width = nil
1397
- bytesize = nil
1398
- if key.instance_of?(String)
1399
- begin
1400
- key.encode(Encoding::UTF_8)
1401
- rescue Encoding::UndefinedConversionError
1402
- return
1403
- end
1404
- str = key
1405
- bytesize = key.bytesize
1406
- else
1407
- begin
1408
- key.chr.encode(Encoding::UTF_8)
1409
- rescue Encoding::UndefinedConversionError
1410
- return
1411
- end
1412
- str = key.chr
1413
- bytesize = 1
1414
- end
1415
- if @in_pasting
1416
- @continuous_insertion_buffer << str
1417
- return
1418
- elsif not @continuous_insertion_buffer.empty?
1419
- process_insert
1420
- end
1421
- width = Reline::Unicode.get_mbchar_width(str)
1422
- if @cursor == @cursor_max
1423
- @line += str
1424
- else
1425
- @line = byteinsert(@line, @byte_pointer, str)
1426
- end
1427
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1428
- @byte_pointer += bytesize
1429
- last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
1430
- if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
1431
- width = 0
1432
- end
1433
- @cursor += width
1434
- @cursor_max += width
1435
- end
1436
- alias_method :ed_digit, :ed_insert
1437
- alias_method :self_insert, :ed_insert
1438
-
1439
- private def ed_quoted_insert(str, arg: 1)
1440
- @waiting_proc = proc { |key|
1441
- arg.times do
1442
- if key == "\C-j".ord or key == "\C-m".ord
1443
- key_newline(key)
1444
- else
1445
- ed_insert(key)
1446
- end
1447
- end
1448
- @waiting_proc = nil
1449
- }
1450
- end
1451
- alias_method :quoted_insert, :ed_quoted_insert
1452
-
1453
- private def ed_next_char(key, arg: 1)
1454
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1455
- if (@byte_pointer < @line.bytesize)
1456
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1457
- width = Reline::Unicode.get_mbchar_width(mbchar)
1458
- @cursor += width if width
1459
- @byte_pointer += byte_size
1460
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
1461
- next_line = @buffer_of_lines[@line_index + 1]
1462
- @cursor = 0
1463
- @byte_pointer = 0
1464
- @cursor_max = calculate_width(next_line)
1465
- @previous_line_index = @line_index
1466
- @line_index += 1
1467
- end
1468
- arg -= 1
1469
- ed_next_char(key, arg: arg) if arg > 0
1470
- end
1471
- alias_method :forward_char, :ed_next_char
1472
-
1473
- private def ed_prev_char(key, arg: 1)
1474
- if @cursor > 0
1475
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1476
- @byte_pointer -= byte_size
1477
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1478
- width = Reline::Unicode.get_mbchar_width(mbchar)
1479
- @cursor -= width
1480
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1481
- prev_line = @buffer_of_lines[@line_index - 1]
1482
- @cursor = calculate_width(prev_line)
1483
- @byte_pointer = prev_line.bytesize
1484
- @cursor_max = calculate_width(prev_line)
1485
- @previous_line_index = @line_index
1486
- @line_index -= 1
1487
- end
1488
- arg -= 1
1489
- ed_prev_char(key, arg: arg) if arg > 0
1490
- end
1491
- alias_method :backward_char, :ed_prev_char
1492
-
1493
- private def vi_first_print(key)
1494
- @byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
1495
- end
1496
-
1497
- private def ed_move_to_beg(key)
1498
- @byte_pointer = @cursor = 0
1499
- end
1500
- alias_method :beginning_of_line, :ed_move_to_beg
1501
-
1502
- private def ed_move_to_end(key)
1503
- @byte_pointer = 0
1504
- @cursor = 0
1505
- byte_size = 0
1506
- while @byte_pointer < @line.bytesize
1507
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1508
- if byte_size > 0
1509
- mbchar = @line.byteslice(@byte_pointer, byte_size)
1510
- @cursor += Reline::Unicode.get_mbchar_width(mbchar)
1511
- end
1512
- @byte_pointer += byte_size
1513
- end
1514
- end
1515
- alias_method :end_of_line, :ed_move_to_end
1516
-
1517
- private def generate_searcher
1518
- Fiber.new do |first_key|
1519
- prev_search_key = first_key
1520
- search_word = String.new(encoding: @encoding)
1521
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1522
- last_hit = nil
1523
- case first_key
1524
- when "\C-r".ord
1525
- prompt_name = 'reverse-i-search'
1526
- when "\C-s".ord
1527
- prompt_name = 'i-search'
1528
- end
1529
- loop do
1530
- key = Fiber.yield(search_word)
1531
- search_again = false
1532
- case key
1533
- when -1 # determined
1534
- Reline.last_incremental_search = search_word
1535
- break
1536
- when "\C-h".ord, "\C-?".ord
1537
- grapheme_clusters = search_word.grapheme_clusters
1538
- if grapheme_clusters.size > 0
1539
- grapheme_clusters.pop
1540
- search_word = grapheme_clusters.join
1541
- end
1542
- when "\C-r".ord, "\C-s".ord
1543
- search_again = true if prev_search_key == key
1544
- prev_search_key = key
1545
- else
1546
- multibyte_buf << key
1547
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1548
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1549
- multibyte_buf.clear
1550
- end
1551
- end
1552
- hit = nil
1553
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1554
- @history_pointer = nil
1555
- hit = @line_backup_in_history
1556
- else
1557
- if search_again
1558
- if search_word.empty? and Reline.last_incremental_search
1559
- search_word = Reline.last_incremental_search
1560
- end
1561
- if @history_pointer
1562
- case prev_search_key
1563
- when "\C-r".ord
1564
- history_pointer_base = 0
1565
- history = Reline::HISTORY[0..(@history_pointer - 1)]
1566
- when "\C-s".ord
1567
- history_pointer_base = @history_pointer + 1
1568
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
1569
- end
1570
- else
1571
- history_pointer_base = 0
1572
- history = Reline::HISTORY
1573
- end
1574
- elsif @history_pointer
1575
- case prev_search_key
1576
- when "\C-r".ord
1577
- history_pointer_base = 0
1578
- history = Reline::HISTORY[0..@history_pointer]
1579
- when "\C-s".ord
1580
- history_pointer_base = @history_pointer
1581
- history = Reline::HISTORY[@history_pointer..-1]
1582
- end
1583
- else
1584
- history_pointer_base = 0
1585
- history = Reline::HISTORY
1586
- end
1587
- case prev_search_key
1588
- when "\C-r".ord
1589
- hit_index = history.rindex { |item|
1590
- item.include?(search_word)
1591
- }
1592
- when "\C-s".ord
1593
- hit_index = history.index { |item|
1594
- item.include?(search_word)
1595
- }
1596
- end
1597
- if hit_index
1598
- @history_pointer = history_pointer_base + hit_index
1599
- hit = Reline::HISTORY[@history_pointer]
1600
- end
1601
- end
1602
- case prev_search_key
1603
- when "\C-r".ord
1604
- prompt_name = 'reverse-i-search'
1605
- when "\C-s".ord
1606
- prompt_name = 'i-search'
1607
- end
1608
- if hit
1609
- if @is_multiline
1610
- @buffer_of_lines = hit.split("\n")
1611
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1612
- @line_index = @buffer_of_lines.size - 1
1613
- @line = @buffer_of_lines.last
1614
- @rerender_all = true
1615
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1616
- else
1617
- @line = hit
1618
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1619
- end
1620
- last_hit = hit
1621
- else
1622
- if @is_multiline
1623
- @rerender_all = true
1624
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1625
- else
1626
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1627
- end
1628
- end
1629
- end
1630
- end
1631
- end
1632
-
1633
- private def incremental_search_history(key)
1634
- unless @history_pointer
1635
- if @is_multiline
1636
- @line_backup_in_history = whole_buffer
1637
- else
1638
- @line_backup_in_history = @line
1639
- end
1640
- end
1641
- searcher = generate_searcher
1642
- searcher.resume(key)
1643
- @searching_prompt = "(reverse-i-search)`': "
1644
- termination_keys = ["\C-j".ord]
1645
- termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
1646
- @waiting_proc = ->(k) {
1647
- case k
1648
- when *termination_keys
1649
- if @history_pointer
1650
- buffer = Reline::HISTORY[@history_pointer]
1651
- else
1652
- buffer = @line_backup_in_history
1653
- end
1654
- if @is_multiline
1655
- @buffer_of_lines = buffer.split("\n")
1656
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1657
- @line_index = @buffer_of_lines.size - 1
1658
- @line = @buffer_of_lines.last
1659
- @rerender_all = true
1660
- else
1661
- @line = buffer
1662
- end
1663
- @searching_prompt = nil
1664
- @waiting_proc = nil
1665
- @cursor_max = calculate_width(@line)
1666
- @cursor = @byte_pointer = 0
1667
- @rerender_all = true
1668
- @cached_prompt_list = nil
1669
- searcher.resume(-1)
1670
- when "\C-g".ord
1671
- if @is_multiline
1672
- @buffer_of_lines = @line_backup_in_history.split("\n")
1673
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1674
- @line_index = @buffer_of_lines.size - 1
1675
- @line = @buffer_of_lines.last
1676
- @rerender_all = true
1677
- else
1678
- @line = @line_backup_in_history
1679
- end
1680
- @history_pointer = nil
1681
- @searching_prompt = nil
1682
- @waiting_proc = nil
1683
- @line_backup_in_history = nil
1684
- @cursor_max = calculate_width(@line)
1685
- @cursor = @byte_pointer = 0
1686
- @rerender_all = true
1687
- else
1688
- chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1689
- if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1690
- searcher.resume(k)
1691
- else
1692
- if @history_pointer
1693
- line = Reline::HISTORY[@history_pointer]
1694
- else
1695
- line = @line_backup_in_history
1696
- end
1697
- if @is_multiline
1698
- @line_backup_in_history = whole_buffer
1699
- @buffer_of_lines = line.split("\n")
1700
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1701
- @line_index = @buffer_of_lines.size - 1
1702
- @line = @buffer_of_lines.last
1703
- @rerender_all = true
1704
- else
1705
- @line_backup_in_history = @line
1706
- @line = line
1707
- end
1708
- @searching_prompt = nil
1709
- @waiting_proc = nil
1710
- @cursor_max = calculate_width(@line)
1711
- @cursor = @byte_pointer = 0
1712
- @rerender_all = true
1713
- @cached_prompt_list = nil
1714
- searcher.resume(-1)
1715
- end
1716
- end
1717
- }
1718
- end
1719
-
1720
- private def vi_search_prev(key)
1721
- incremental_search_history(key)
1722
- end
1723
- alias_method :reverse_search_history, :vi_search_prev
1724
-
1725
- private def vi_search_next(key)
1726
- incremental_search_history(key)
1727
- end
1728
- alias_method :forward_search_history, :vi_search_next
1729
-
1730
- private def ed_search_prev_history(key, arg: 1)
1731
- history = nil
1732
- h_pointer = nil
1733
- line_no = nil
1734
- substr = @line.slice(0, @byte_pointer)
1735
- if @history_pointer.nil?
1736
- return if not @line.empty? and substr.empty?
1737
- history = Reline::HISTORY
1738
- elsif @history_pointer.zero?
1739
- history = nil
1740
- h_pointer = nil
1741
- else
1742
- history = Reline::HISTORY.slice(0, @history_pointer)
1743
- end
1744
- return if history.nil?
1745
- if @is_multiline
1746
- h_pointer = history.rindex { |h|
1747
- h.split("\n").each_with_index { |l, i|
1748
- if l.start_with?(substr)
1749
- line_no = i
1750
- break
1751
- end
1752
- }
1753
- not line_no.nil?
1754
- }
1755
- else
1756
- h_pointer = history.rindex { |l|
1757
- l.start_with?(substr)
1758
- }
1759
- end
1760
- return if h_pointer.nil?
1761
- @history_pointer = h_pointer
1762
- if @is_multiline
1763
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1764
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1765
- @line_index = line_no
1766
- @line = @buffer_of_lines[@line_index]
1767
- @rerender_all = true
1768
- else
1769
- @line = Reline::HISTORY[@history_pointer]
1770
- end
1771
- @cursor_max = calculate_width(@line)
1772
- arg -= 1
1773
- ed_search_prev_history(key, arg: arg) if arg > 0
1774
- end
1775
- alias_method :history_search_backward, :ed_search_prev_history
1776
-
1777
- private def ed_search_next_history(key, arg: 1)
1778
- substr = @line.slice(0, @byte_pointer)
1779
- if @history_pointer.nil?
1780
- return
1781
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1782
- return
1783
- end
1784
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1785
- h_pointer = nil
1786
- line_no = nil
1787
- if @is_multiline
1788
- h_pointer = history.index { |h|
1789
- h.split("\n").each_with_index { |l, i|
1790
- if l.start_with?(substr)
1791
- line_no = i
1792
- break
1793
- end
1794
- }
1795
- not line_no.nil?
1796
- }
1797
- else
1798
- h_pointer = history.index { |l|
1799
- l.start_with?(substr)
1800
- }
1801
- end
1802
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1803
- return if h_pointer.nil? and not substr.empty?
1804
- @history_pointer = h_pointer
1805
- if @is_multiline
1806
- if @history_pointer.nil? and substr.empty?
1807
- @buffer_of_lines = []
1808
- @line_index = 0
1809
- else
1810
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1811
- @line_index = line_no
1812
- end
1813
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1814
- @line = @buffer_of_lines[@line_index]
1815
- @rerender_all = true
1816
- else
1817
- if @history_pointer.nil? and substr.empty?
1818
- @line = ''
1819
- else
1820
- @line = Reline::HISTORY[@history_pointer]
1821
- end
1822
- end
1823
- @cursor_max = calculate_width(@line)
1824
- arg -= 1
1825
- ed_search_next_history(key, arg: arg) if arg > 0
1826
- end
1827
- alias_method :history_search_forward, :ed_search_next_history
1828
-
1829
- private def ed_prev_history(key, arg: 1)
1830
- if @is_multiline and @line_index > 0
1831
- @previous_line_index = @line_index
1832
- @line_index -= 1
1833
- return
1834
- end
1835
- if Reline::HISTORY.empty?
1836
- return
1837
- end
1838
- if @history_pointer.nil?
1839
- @history_pointer = Reline::HISTORY.size - 1
1840
- if @is_multiline
1841
- @line_backup_in_history = whole_buffer
1842
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1843
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1844
- @line_index = @buffer_of_lines.size - 1
1845
- @line = @buffer_of_lines.last
1846
- @rerender_all = true
1847
- else
1848
- @line_backup_in_history = @line
1849
- @line = Reline::HISTORY[@history_pointer]
1850
- end
1851
- elsif @history_pointer.zero?
1852
- return
1853
- else
1854
- if @is_multiline
1855
- Reline::HISTORY[@history_pointer] = whole_buffer
1856
- @history_pointer -= 1
1857
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1858
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1859
- @line_index = @buffer_of_lines.size - 1
1860
- @line = @buffer_of_lines.last
1861
- @rerender_all = true
1862
- else
1863
- Reline::HISTORY[@history_pointer] = @line
1864
- @history_pointer -= 1
1865
- @line = Reline::HISTORY[@history_pointer]
1866
- end
1867
- end
1868
- if @config.editing_mode_is?(:emacs, :vi_insert)
1869
- @cursor_max = @cursor = calculate_width(@line)
1870
- @byte_pointer = @line.bytesize
1871
- elsif @config.editing_mode_is?(:vi_command)
1872
- @byte_pointer = @cursor = 0
1873
- @cursor_max = calculate_width(@line)
1874
- end
1875
- arg -= 1
1876
- ed_prev_history(key, arg: arg) if arg > 0
1877
- end
1878
-
1879
- private def ed_next_history(key, arg: 1)
1880
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1881
- @previous_line_index = @line_index
1882
- @line_index += 1
1883
- return
1884
- end
1885
- if @history_pointer.nil?
1886
- return
1887
- elsif @history_pointer == (Reline::HISTORY.size - 1)
1888
- if @is_multiline
1889
- @history_pointer = nil
1890
- @buffer_of_lines = @line_backup_in_history.split("\n")
1891
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1892
- @line_index = 0
1893
- @line = @buffer_of_lines.first
1894
- @rerender_all = true
1895
- else
1896
- @history_pointer = nil
1897
- @line = @line_backup_in_history
1898
- end
1899
- else
1900
- if @is_multiline
1901
- Reline::HISTORY[@history_pointer] = whole_buffer
1902
- @history_pointer += 1
1903
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1904
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1905
- @line_index = 0
1906
- @line = @buffer_of_lines.first
1907
- @rerender_all = true
1908
- else
1909
- Reline::HISTORY[@history_pointer] = @line
1910
- @history_pointer += 1
1911
- @line = Reline::HISTORY[@history_pointer]
1912
- end
1913
- end
1914
- @line = '' unless @line
1915
- if @config.editing_mode_is?(:emacs, :vi_insert)
1916
- @cursor_max = @cursor = calculate_width(@line)
1917
- @byte_pointer = @line.bytesize
1918
- elsif @config.editing_mode_is?(:vi_command)
1919
- @byte_pointer = @cursor = 0
1920
- @cursor_max = calculate_width(@line)
1921
- end
1922
- arg -= 1
1923
- ed_next_history(key, arg: arg) if arg > 0
1924
- end
1925
-
1926
- private def ed_newline(key)
1927
- process_insert(force: true)
1928
- if @is_multiline
1929
- if @config.editing_mode_is?(:vi_command)
1930
- if @line_index < (@buffer_of_lines.size - 1)
1931
- ed_next_history(key) # means cursor down
1932
- else
1933
- # should check confirm_multiline_termination to finish?
1934
- finish
1935
- end
1936
- else
1937
- if @line_index == (@buffer_of_lines.size - 1)
1938
- if confirm_multiline_termination
1939
- finish
1940
- else
1941
- key_newline(key)
1942
- end
1943
- else
1944
- # should check confirm_multiline_termination to finish?
1945
- @previous_line_index = @line_index
1946
- @line_index = @buffer_of_lines.size - 1
1947
- finish
1948
- end
1949
- end
1950
- else
1951
- if @history_pointer
1952
- Reline::HISTORY[@history_pointer] = @line
1953
- @history_pointer = nil
1954
- end
1955
- finish
1956
- end
1957
- end
1958
-
1959
- private def em_delete_prev_char(key)
1960
- if @is_multiline and @cursor == 0 and @line_index > 0
1961
- @buffer_of_lines[@line_index] = @line
1962
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
1963
- @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1964
- @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1965
- @line_index -= 1
1966
- @line = @buffer_of_lines[@line_index]
1967
- @cursor_max = calculate_width(@line)
1968
- @rerender_all = true
1969
- elsif @cursor > 0
1970
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
1971
- @byte_pointer -= byte_size
1972
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1973
- width = Reline::Unicode.get_mbchar_width(mbchar)
1974
- @cursor -= width
1975
- @cursor_max -= width
1976
- end
1977
- end
1978
- alias_method :backward_delete_char, :em_delete_prev_char
1979
-
1980
- private def ed_kill_line(key)
1981
- if @line.bytesize > @byte_pointer
1982
- @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
1983
- @byte_pointer = @line.bytesize
1984
- @cursor = @cursor_max = calculate_width(@line)
1985
- @kill_ring.append(deleted)
1986
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
1987
- @cursor = calculate_width(@line)
1988
- @byte_pointer = @line.bytesize
1989
- @line += @buffer_of_lines.delete_at(@line_index + 1)
1990
- @cursor_max = calculate_width(@line)
1991
- @buffer_of_lines[@line_index] = @line
1992
- @rerender_all = true
1993
- @rest_height += 1
1994
- end
1995
- end
1996
-
1997
- private def em_kill_line(key)
1998
- if @byte_pointer > 0
1999
- @line, deleted = byteslice!(@line, 0, @byte_pointer)
2000
- @byte_pointer = 0
2001
- @kill_ring.append(deleted, true)
2002
- @cursor_max = calculate_width(@line)
2003
- @cursor = 0
2004
- end
2005
- end
2006
- alias_method :kill_line, :em_kill_line
2007
-
2008
- private def em_delete(key)
2009
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2010
- @line = nil
2011
- if @buffer_of_lines.size > 1
2012
- scroll_down(@highest_in_all - @first_line_started_from)
2013
- end
2014
- Reline::IOGate.move_cursor_column(0)
2015
- @eof = true
2016
- finish
2017
- elsif @byte_pointer < @line.bytesize
2018
- splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
2019
- mbchar = splitted_last.grapheme_clusters.first
2020
- width = Reline::Unicode.get_mbchar_width(mbchar)
2021
- @cursor_max -= width
2022
- @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
2023
- elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
2024
- @cursor = calculate_width(@line)
2025
- @byte_pointer = @line.bytesize
2026
- @line += @buffer_of_lines.delete_at(@line_index + 1)
2027
- @cursor_max = calculate_width(@line)
2028
- @buffer_of_lines[@line_index] = @line
2029
- @rerender_all = true
2030
- @rest_height += 1
2031
- end
2032
- end
2033
- alias_method :delete_char, :em_delete
2034
-
2035
- private def em_delete_or_list(key)
2036
- if @line.empty? or @byte_pointer < @line.bytesize
2037
- em_delete(key)
2038
- else # show completed list
2039
- result = call_completion_proc
2040
- if result.is_a?(Array)
2041
- complete(result, true)
2042
- end
2043
- end
2044
- end
2045
- alias_method :delete_char_or_list, :em_delete_or_list
2046
-
2047
- private def em_yank(key)
2048
- yanked = @kill_ring.yank
2049
- if yanked
2050
- @line = byteinsert(@line, @byte_pointer, yanked)
2051
- yanked_width = calculate_width(yanked)
2052
- @cursor += yanked_width
2053
- @cursor_max += yanked_width
2054
- @byte_pointer += yanked.bytesize
2055
- end
2056
- end
2057
- alias_method :yank, :em_yank
2058
-
2059
- private def em_yank_pop(key)
2060
- yanked, prev_yank = @kill_ring.yank_pop
2061
- if yanked
2062
- prev_yank_width = calculate_width(prev_yank)
2063
- @cursor -= prev_yank_width
2064
- @cursor_max -= prev_yank_width
2065
- @byte_pointer -= prev_yank.bytesize
2066
- @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
2067
- @line = byteinsert(@line, @byte_pointer, yanked)
2068
- yanked_width = calculate_width(yanked)
2069
- @cursor += yanked_width
2070
- @cursor_max += yanked_width
2071
- @byte_pointer += yanked.bytesize
2072
- end
2073
- end
2074
- alias_method :yank_pop, :em_yank_pop
2075
-
2076
- private def ed_clear_screen(key)
2077
- @cleared = true
2078
- end
2079
- alias_method :clear_screen, :ed_clear_screen
2080
-
2081
- private def em_next_word(key)
2082
- if @line.bytesize > @byte_pointer
2083
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2084
- @byte_pointer += byte_size
2085
- @cursor += width
2086
- end
2087
- end
2088
- alias_method :forward_word, :em_next_word
2089
-
2090
- private def ed_prev_word(key)
2091
- if @byte_pointer > 0
2092
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2093
- @byte_pointer -= byte_size
2094
- @cursor -= width
2095
- end
2096
- end
2097
- alias_method :backward_word, :ed_prev_word
2098
-
2099
- private def em_delete_next_word(key)
2100
- if @line.bytesize > @byte_pointer
2101
- byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2102
- @line, word = byteslice!(@line, @byte_pointer, byte_size)
2103
- @kill_ring.append(word)
2104
- @cursor_max -= width
2105
- end
2106
- end
2107
-
2108
- private def ed_delete_prev_word(key)
2109
- if @byte_pointer > 0
2110
- byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
2111
- @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2112
- @kill_ring.append(word, true)
2113
- @byte_pointer -= byte_size
2114
- @cursor -= width
2115
- @cursor_max -= width
2116
- end
2117
- end
2118
-
2119
- private def ed_transpose_chars(key)
2120
- if @byte_pointer > 0
2121
- if @cursor_max > @cursor
2122
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2123
- mbchar = @line.byteslice(@byte_pointer, byte_size)
2124
- width = Reline::Unicode.get_mbchar_width(mbchar)
2125
- @cursor += width
2126
- @byte_pointer += byte_size
2127
- end
2128
- back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2129
- if (@byte_pointer - back1_byte_size) > 0
2130
- back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
2131
- back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
2132
- @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
2133
- @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
2134
- end
2135
- end
2136
- end
2137
- alias_method :transpose_chars, :ed_transpose_chars
2138
-
2139
- private def ed_transpose_words(key)
2140
- left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
2141
- before = @line.byteslice(0, left_word_start)
2142
- left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
2143
- middle = @line.byteslice(middle_start, right_word_start - middle_start)
2144
- right_word = @line.byteslice(right_word_start, after_start - right_word_start)
2145
- after = @line.byteslice(after_start, @line.bytesize - after_start)
2146
- return if left_word.empty? or right_word.empty?
2147
- @line = before + right_word + middle + left_word + after
2148
- from_head_to_left_word = before + right_word + middle + left_word
2149
- @byte_pointer = from_head_to_left_word.bytesize
2150
- @cursor = calculate_width(from_head_to_left_word)
2151
- end
2152
- alias_method :transpose_words, :ed_transpose_words
2153
-
2154
- private def em_capitol_case(key)
2155
- if @line.bytesize > @byte_pointer
2156
- byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
2157
- before = @line.byteslice(0, @byte_pointer)
2158
- after = @line.byteslice((@byte_pointer + byte_size)..-1)
2159
- @line = before + new_str + after
2160
- @byte_pointer += new_str.bytesize
2161
- @cursor += calculate_width(new_str)
2162
- end
2163
- end
2164
- alias_method :capitalize_word, :em_capitol_case
2165
-
2166
- private def em_lower_case(key)
2167
- if @line.bytesize > @byte_pointer
2168
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2169
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2170
- mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
2171
- }.join
2172
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2173
- @line = @line.byteslice(0, @byte_pointer) + part
2174
- @byte_pointer = @line.bytesize
2175
- @cursor = calculate_width(@line)
2176
- @cursor_max = @cursor + calculate_width(rest)
2177
- @line += rest
2178
- end
2179
- end
2180
- alias_method :downcase_word, :em_lower_case
2181
-
2182
- private def em_upper_case(key)
2183
- if @line.bytesize > @byte_pointer
2184
- byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
2185
- part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
2186
- mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
2187
- }.join
2188
- rest = @line.byteslice((@byte_pointer + byte_size)..-1)
2189
- @line = @line.byteslice(0, @byte_pointer) + part
2190
- @byte_pointer = @line.bytesize
2191
- @cursor = calculate_width(@line)
2192
- @cursor_max = @cursor + calculate_width(rest)
2193
- @line += rest
2194
- end
2195
- end
2196
- alias_method :upcase_word, :em_upper_case
2197
-
2198
- private def em_kill_region(key)
2199
- if @byte_pointer > 0
2200
- byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
2201
- @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
2202
- @byte_pointer -= byte_size
2203
- @cursor -= width
2204
- @cursor_max -= width
2205
- @kill_ring.append(deleted, true)
2206
- end
2207
- end
2208
- alias_method :unix_word_rubout, :em_kill_region
2209
-
2210
- private def copy_for_vi(text)
2211
- if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
2212
- @vi_clipboard = text
2213
- end
2214
- end
2215
-
2216
- private def vi_insert(key)
2217
- @config.editing_mode = :vi_insert
2218
- end
2219
-
2220
- private def vi_add(key)
2221
- @config.editing_mode = :vi_insert
2222
- ed_next_char(key)
2223
- end
2224
-
2225
- private def vi_command_mode(key)
2226
- ed_prev_char(key)
2227
- @config.editing_mode = :vi_command
2228
- end
2229
- alias_method :vi_movement_mode, :vi_command_mode
2230
-
2231
- private def vi_next_word(key, arg: 1)
2232
- if @line.bytesize > @byte_pointer
2233
- byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
2234
- @byte_pointer += byte_size
2235
- @cursor += width
2236
- end
2237
- arg -= 1
2238
- vi_next_word(key, arg: arg) if arg > 0
2239
- end
2240
-
2241
- private def vi_prev_word(key, arg: 1)
2242
- if @byte_pointer > 0
2243
- byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
2244
- @byte_pointer -= byte_size
2245
- @cursor -= width
2246
- end
2247
- arg -= 1
2248
- vi_prev_word(key, arg: arg) if arg > 0
2249
- end
2250
-
2251
- private def vi_end_word(key, arg: 1, inclusive: false)
2252
- if @line.bytesize > @byte_pointer
2253
- byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
2254
- @byte_pointer += byte_size
2255
- @cursor += width
2256
- end
2257
- arg -= 1
2258
- if inclusive and arg.zero?
2259
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2260
- if byte_size > 0
2261
- c = @line.byteslice(@byte_pointer, byte_size)
2262
- width = Reline::Unicode.get_mbchar_width(c)
2263
- @byte_pointer += byte_size
2264
- @cursor += width
2265
- end
2266
- end
2267
- vi_end_word(key, arg: arg) if arg > 0
2268
- end
2269
-
2270
- private def vi_next_big_word(key, arg: 1)
2271
- if @line.bytesize > @byte_pointer
2272
- byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
2273
- @byte_pointer += byte_size
2274
- @cursor += width
2275
- end
2276
- arg -= 1
2277
- vi_next_big_word(key, arg: arg) if arg > 0
2278
- end
2279
-
2280
- private def vi_prev_big_word(key, arg: 1)
2281
- if @byte_pointer > 0
2282
- byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
2283
- @byte_pointer -= byte_size
2284
- @cursor -= width
2285
- end
2286
- arg -= 1
2287
- vi_prev_big_word(key, arg: arg) if arg > 0
2288
- end
2289
-
2290
- private def vi_end_big_word(key, arg: 1, inclusive: false)
2291
- if @line.bytesize > @byte_pointer
2292
- byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
2293
- @byte_pointer += byte_size
2294
- @cursor += width
2295
- end
2296
- arg -= 1
2297
- if inclusive and arg.zero?
2298
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2299
- if byte_size > 0
2300
- c = @line.byteslice(@byte_pointer, byte_size)
2301
- width = Reline::Unicode.get_mbchar_width(c)
2302
- @byte_pointer += byte_size
2303
- @cursor += width
2304
- end
2305
- end
2306
- vi_end_big_word(key, arg: arg) if arg > 0
2307
- end
2308
-
2309
- private def vi_delete_prev_char(key)
2310
- if @is_multiline and @cursor == 0 and @line_index > 0
2311
- @buffer_of_lines[@line_index] = @line
2312
- @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
2313
- @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2314
- @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2315
- @line_index -= 1
2316
- @line = @buffer_of_lines[@line_index]
2317
- @cursor_max = calculate_width(@line)
2318
- @rerender_all = true
2319
- elsif @cursor > 0
2320
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2321
- @byte_pointer -= byte_size
2322
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2323
- width = Reline::Unicode.get_mbchar_width(mbchar)
2324
- @cursor -= width
2325
- @cursor_max -= width
2326
- end
2327
- end
2328
-
2329
- private def vi_insert_at_bol(key)
2330
- ed_move_to_beg(key)
2331
- @config.editing_mode = :vi_insert
2332
- end
2333
-
2334
- private def vi_add_at_eol(key)
2335
- ed_move_to_end(key)
2336
- @config.editing_mode = :vi_insert
2337
- end
2338
-
2339
- private def ed_delete_prev_char(key, arg: 1)
2340
- deleted = ''
2341
- arg.times do
2342
- if @cursor > 0
2343
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2344
- @byte_pointer -= byte_size
2345
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2346
- deleted.prepend(mbchar)
2347
- width = Reline::Unicode.get_mbchar_width(mbchar)
2348
- @cursor -= width
2349
- @cursor_max -= width
2350
- end
2351
- end
2352
- copy_for_vi(deleted)
2353
- end
2354
-
2355
- private def vi_zero(key)
2356
- @byte_pointer = 0
2357
- @cursor = 0
2358
- end
2359
-
2360
- private def vi_change_meta(key, arg: 1)
2361
- @drop_terminate_spaces = true
2362
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2363
- if byte_pointer_diff > 0
2364
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2365
- elsif byte_pointer_diff < 0
2366
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2367
- end
2368
- copy_for_vi(cut)
2369
- @cursor += cursor_diff if cursor_diff < 0
2370
- @cursor_max -= cursor_diff.abs
2371
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2372
- @config.editing_mode = :vi_insert
2373
- @drop_terminate_spaces = false
2374
- }
2375
- @waiting_operator_vi_arg = arg
2376
- end
2377
-
2378
- private def vi_delete_meta(key, arg: 1)
2379
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2380
- if byte_pointer_diff > 0
2381
- @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
2382
- elsif byte_pointer_diff < 0
2383
- @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2384
- end
2385
- copy_for_vi(cut)
2386
- @cursor += cursor_diff if cursor_diff < 0
2387
- @cursor_max -= cursor_diff.abs
2388
- @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
2389
- }
2390
- @waiting_operator_vi_arg = arg
2391
- end
2392
-
2393
- private def vi_yank(key, arg: 1)
2394
- @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
2395
- if byte_pointer_diff > 0
2396
- cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
2397
- elsif byte_pointer_diff < 0
2398
- cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2399
- end
2400
- copy_for_vi(cut)
2401
- }
2402
- @waiting_operator_vi_arg = arg
2403
- end
2404
-
2405
- private def vi_list_or_eof(key)
2406
- if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
2407
- @line = nil
2408
- if @buffer_of_lines.size > 1
2409
- scroll_down(@highest_in_all - @first_line_started_from)
2410
- end
2411
- Reline::IOGate.move_cursor_column(0)
2412
- @eof = true
2413
- finish
2414
- else
2415
- ed_newline(key)
2416
- end
2417
- end
2418
- alias_method :vi_end_of_transmission, :vi_list_or_eof
2419
- alias_method :vi_eof_maybe, :vi_list_or_eof
2420
-
2421
- private def ed_delete_next_char(key, arg: 1)
2422
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2423
- unless @line.empty? || byte_size == 0
2424
- @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
2425
- copy_for_vi(mbchar)
2426
- width = Reline::Unicode.get_mbchar_width(mbchar)
2427
- @cursor_max -= width
2428
- if @cursor > 0 and @cursor >= @cursor_max
2429
- byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2430
- mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
2431
- width = Reline::Unicode.get_mbchar_width(mbchar)
2432
- @byte_pointer -= byte_size
2433
- @cursor -= width
2434
- end
2435
- end
2436
- arg -= 1
2437
- ed_delete_next_char(key, arg: arg) if arg > 0
2438
- end
2439
-
2440
- private def vi_to_history_line(key)
2441
- if Reline::HISTORY.empty?
2442
- return
2443
- end
2444
- if @history_pointer.nil?
2445
- @history_pointer = 0
2446
- @line_backup_in_history = @line
2447
- @line = Reline::HISTORY[@history_pointer]
2448
- @cursor_max = calculate_width(@line)
2449
- @cursor = 0
2450
- @byte_pointer = 0
2451
- elsif @history_pointer.zero?
2452
- return
2453
- else
2454
- Reline::HISTORY[@history_pointer] = @line
2455
- @history_pointer = 0
2456
- @line = Reline::HISTORY[@history_pointer]
2457
- @cursor_max = calculate_width(@line)
2458
- @cursor = 0
2459
- @byte_pointer = 0
2460
- end
2461
- end
2462
-
2463
- private def vi_histedit(key)
2464
- path = Tempfile.open { |fp|
2465
- if @is_multiline
2466
- fp.write whole_lines.join("\n")
2467
- else
2468
- fp.write @line
2469
- end
2470
- fp.path
2471
- }
2472
- system("#{ENV['EDITOR']} #{path}")
2473
- if @is_multiline
2474
- @buffer_of_lines = File.read(path).split("\n")
2475
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2476
- @line_index = 0
2477
- @line = @buffer_of_lines[@line_index]
2478
- @rerender_all = true
2479
- else
2480
- @line = File.read(path)
2481
- end
2482
- finish
2483
- end
2484
-
2485
- private def vi_paste_prev(key, arg: 1)
2486
- if @vi_clipboard.size > 0
2487
- @line = byteinsert(@line, @byte_pointer, @vi_clipboard)
2488
- @cursor_max += calculate_width(@vi_clipboard)
2489
- cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
2490
- @cursor += calculate_width(cursor_point)
2491
- @byte_pointer += cursor_point.bytesize
2492
- end
2493
- arg -= 1
2494
- vi_paste_prev(key, arg: arg) if arg > 0
2495
- end
2496
-
2497
- private def vi_paste_next(key, arg: 1)
2498
- if @vi_clipboard.size > 0
2499
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2500
- @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
2501
- @cursor_max += calculate_width(@vi_clipboard)
2502
- @cursor += calculate_width(@vi_clipboard)
2503
- @byte_pointer += @vi_clipboard.bytesize
2504
- end
2505
- arg -= 1
2506
- vi_paste_next(key, arg: arg) if arg > 0
2507
- end
2508
-
2509
- private def ed_argument_digit(key)
2510
- if @vi_arg.nil?
2511
- unless key.chr.to_i.zero?
2512
- @vi_arg = key.chr.to_i
2513
- end
2514
- else
2515
- @vi_arg = @vi_arg * 10 + key.chr.to_i
2516
- end
2517
- end
2518
-
2519
- private def vi_to_column(key, arg: 0)
2520
- @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
2521
- # total has [byte_size, cursor]
2522
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
2523
- if (total.last + mbchar_width) >= arg
2524
- break total
2525
- elsif (total.last + mbchar_width) >= @cursor_max
2526
- break total
2527
- else
2528
- total = [total.first + gc.bytesize, total.last + mbchar_width]
2529
- total
2530
- end
2531
- }
2532
- end
2533
-
2534
- private def vi_replace_char(key, arg: 1)
2535
- @waiting_proc = ->(k) {
2536
- if arg == 1
2537
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2538
- before = @line.byteslice(0, @byte_pointer)
2539
- remaining_point = @byte_pointer + byte_size
2540
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2541
- @line = before + k.chr + after
2542
- @cursor_max = calculate_width(@line)
2543
- @waiting_proc = nil
2544
- elsif arg > 1
2545
- byte_size = 0
2546
- arg.times do
2547
- byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
2548
- end
2549
- before = @line.byteslice(0, @byte_pointer)
2550
- remaining_point = @byte_pointer + byte_size
2551
- after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
2552
- replaced = k.chr * arg
2553
- @line = before + replaced + after
2554
- @byte_pointer += replaced.bytesize
2555
- @cursor += calculate_width(replaced)
2556
- @cursor_max = calculate_width(@line)
2557
- @waiting_proc = nil
2558
- end
2559
- }
2560
- end
2561
-
2562
- private def vi_next_char(key, arg: 1, inclusive: false)
2563
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2564
- end
2565
-
2566
- private def vi_to_next_char(key, arg: 1, inclusive: false)
2567
- @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2568
- end
2569
-
2570
- private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2571
- if key.instance_of?(String)
2572
- inputed_char = key
2573
- else
2574
- inputed_char = key.chr
2575
- end
2576
- prev_total = nil
2577
- total = nil
2578
- found = false
2579
- @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2580
- # total has [byte_size, cursor]
2581
- unless total
2582
- # skip cursor point
2583
- width = Reline::Unicode.get_mbchar_width(mbchar)
2584
- total = [mbchar.bytesize, width]
2585
- else
2586
- if inputed_char == mbchar
2587
- arg -= 1
2588
- if arg.zero?
2589
- found = true
2590
- break
2591
- end
2592
- end
2593
- width = Reline::Unicode.get_mbchar_width(mbchar)
2594
- prev_total = total
2595
- total = [total.first + mbchar.bytesize, total.last + width]
2596
- end
2597
- end
2598
- if not need_prev_char and found and total
2599
- byte_size, width = total
2600
- @byte_pointer += byte_size
2601
- @cursor += width
2602
- elsif need_prev_char and found and prev_total
2603
- byte_size, width = prev_total
2604
- @byte_pointer += byte_size
2605
- @cursor += width
2606
- end
2607
- if inclusive
2608
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
2609
- if byte_size > 0
2610
- c = @line.byteslice(@byte_pointer, byte_size)
2611
- width = Reline::Unicode.get_mbchar_width(c)
2612
- @byte_pointer += byte_size
2613
- @cursor += width
2614
- end
2615
- end
2616
- @waiting_proc = nil
2617
- end
2618
-
2619
- private def vi_prev_char(key, arg: 1)
2620
- @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
2621
- end
2622
-
2623
- private def vi_to_prev_char(key, arg: 1)
2624
- @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
2625
- end
2626
-
2627
- private def search_prev_char(key, arg, need_next_char = false)
2628
- if key.instance_of?(String)
2629
- inputed_char = key
2630
- else
2631
- inputed_char = key.chr
2632
- end
2633
- prev_total = nil
2634
- total = nil
2635
- found = false
2636
- @line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2637
- # total has [byte_size, cursor]
2638
- unless total
2639
- # skip cursor point
2640
- width = Reline::Unicode.get_mbchar_width(mbchar)
2641
- total = [mbchar.bytesize, width]
2642
- else
2643
- if inputed_char == mbchar
2644
- arg -= 1
2645
- if arg.zero?
2646
- found = true
2647
- break
2648
- end
2649
- end
2650
- width = Reline::Unicode.get_mbchar_width(mbchar)
2651
- prev_total = total
2652
- total = [total.first + mbchar.bytesize, total.last + width]
2653
- end
2654
- end
2655
- if not need_next_char and found and total
2656
- byte_size, width = total
2657
- @byte_pointer -= byte_size
2658
- @cursor -= width
2659
- elsif need_next_char and found and prev_total
2660
- byte_size, width = prev_total
2661
- @byte_pointer -= byte_size
2662
- @cursor -= width
2663
- end
2664
- @waiting_proc = nil
2665
- end
2666
-
2667
- private def vi_join_lines(key, arg: 1)
2668
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
2669
- @cursor = calculate_width(@line)
2670
- @byte_pointer = @line.bytesize
2671
- @line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
2672
- @cursor_max = calculate_width(@line)
2673
- @buffer_of_lines[@line_index] = @line
2674
- @rerender_all = true
2675
- @rest_height += 1
2676
- end
2677
- arg -= 1
2678
- vi_join_lines(key, arg: arg) if arg > 0
2679
- end
2680
-
2681
- private def em_set_mark(key)
2682
- @mark_pointer = [@byte_pointer, @line_index]
2683
- end
2684
- alias_method :set_mark, :em_set_mark
2685
-
2686
- private def em_exchange_mark(key)
2687
- return unless @mark_pointer
2688
- new_pointer = [@byte_pointer, @line_index]
2689
- @previous_line_index = @line_index
2690
- @byte_pointer, @line_index = @mark_pointer
2691
- @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
2692
- @cursor_max = calculate_width(@line)
2693
- @mark_pointer = new_pointer
2694
- end
2695
- alias_method :exchange_point_and_mark, :em_exchange_mark
2696
- end