reline 0.2.0 → 0.2.1

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