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