reline 0.1.7 → 0.2.1

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