reline 0.2.4 → 0.2.5

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