reline 0.2.8.pre.8 → 0.2.8.pre.9

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