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