reline 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68e79c30bbef8d97090f7126f96c5ee0c32fa95e6dc120a91240a6e9ff3864c6
4
- data.tar.gz: 857716d3f9ab324efef98d3cb42acf2bba17848012a832c5631ca3e6d2f15733
3
+ metadata.gz: 45922535b3972631020c6b33bc6b88fd400062fb2c0820ab95ab991704c1fc42
4
+ data.tar.gz: 53f36ad5e7e196ba34a22990c32b38556653b7384bd99108a791d1b4c7cd3b36
5
5
  SHA512:
6
- metadata.gz: b25e6a151ded60963f660ccafceef8c221577b9f2328b9daa10dc381f5ea63c293a9759a786ab02610eb2fb07c52f3a200303fd9ac672210658884cb9a57c88c
7
- data.tar.gz: 8433437480cf5acf7d87aefc632cc641e9325f163ab1d00d1df9cea5d5f3977f05250b20b366d7fd21ae275edd8f152ddc5879fa88340fbf35990e9b2a7e9725
6
+ metadata.gz: eb1b50add8dc60baeefd1fecce83abbaf0e134c16c30ca7fd268efe46d364638280bd2a16100e54be6dbf691bc8a9d4220c53210233a06a0278361c07bd5a56f
7
+ data.tar.gz: 5ed4f57373cd6e9329de58b9361037e4f8e510020c0ba59970de5733a8c8f4b5c004b871d8e21f013ee065daed9b55f433142529b5e4039276a0f0adbc00c4d9
data/lib/reline/ansi.rb CHANGED
@@ -284,7 +284,7 @@ class Reline::ANSI
284
284
  buf = @@output.pread(@@output.pos, 0)
285
285
  row = buf.count("\n")
286
286
  column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
287
- rescue Errno::ESPIPE
287
+ rescue Errno::ESPIPE, IOError
288
288
  # Just returns column 1 for ambiguous width because this I/O is not
289
289
  # tty and can't seek.
290
290
  row = 0
data/lib/reline/config.rb CHANGED
@@ -53,8 +53,6 @@ class Reline::Config
53
53
  @additional_key_bindings[:vi_insert] = {}
54
54
  @additional_key_bindings[:vi_command] = {}
55
55
  @oneshot_key_bindings = {}
56
- @skip_section = nil
57
- @if_stack = nil
58
56
  @editing_mode_label = :emacs
59
57
  @keymap_label = :emacs
60
58
  @keymap_prefix = []
@@ -190,9 +188,7 @@ class Reline::Config
190
188
  end
191
189
  end
192
190
  end
193
- conditions = [@skip_section, @if_stack]
194
- @skip_section = nil
195
- @if_stack = []
191
+ if_stack = []
196
192
 
197
193
  lines.each_with_index do |line, no|
198
194
  next if line.match(/\A\s*#/)
@@ -201,11 +197,11 @@ class Reline::Config
201
197
 
202
198
  line = line.chomp.lstrip
203
199
  if line.start_with?('$')
204
- handle_directive(line[1..-1], file, no)
200
+ handle_directive(line[1..-1], file, no, if_stack)
205
201
  next
206
202
  end
207
203
 
208
- next if @skip_section
204
+ next if if_stack.any? { |_no, skip| skip }
209
205
 
210
206
  case line
211
207
  when /^set +([^ ]+) +([^ ]+)/i
@@ -214,43 +210,47 @@ class Reline::Config
214
210
  next
215
211
  when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
216
212
  key, func_name = $1, $2
213
+ func_name = func_name.split.first
217
214
  keystroke, func = bind_key(key, func_name)
218
215
  next unless keystroke
219
216
  @additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
220
217
  end
221
218
  end
222
- unless @if_stack.empty?
223
- raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
219
+ unless if_stack.empty?
220
+ raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
224
221
  end
225
- ensure
226
- @skip_section, @if_stack = conditions
227
222
  end
228
223
 
229
- def handle_directive(directive, file, no)
224
+ def handle_directive(directive, file, no, if_stack)
230
225
  directive, args = directive.split(' ')
231
226
  case directive
232
227
  when 'if'
233
228
  condition = false
234
229
  case args
235
- when 'mode'
230
+ when /^mode=(vi|emacs)$/i
231
+ mode = $1.downcase
232
+ # NOTE: mode=vi means vi-insert mode
233
+ mode = 'vi_insert' if mode == 'vi'
234
+ if @editing_mode_label == mode.to_sym
235
+ condition = true
236
+ end
236
237
  when 'term'
237
238
  when 'version'
238
239
  else # application name
239
240
  condition = true if args == 'Ruby'
240
241
  condition = true if args == 'Reline'
241
242
  end
242
- @if_stack << [file, no, @skip_section]
243
- @skip_section = !condition
243
+ if_stack << [no, !condition]
244
244
  when 'else'
245
- if @if_stack.empty?
245
+ if if_stack.empty?
246
246
  raise InvalidInputrc, "#{file}:#{no}: unmatched else"
247
247
  end
248
- @skip_section = !@skip_section
248
+ if_stack.last[1] = !if_stack.last[1]
249
249
  when 'endif'
250
- if @if_stack.empty?
250
+ if if_stack.empty?
251
251
  raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
252
252
  end
253
- @skip_section = @if_stack.pop
253
+ if_stack.pop
254
254
  when 'include'
255
255
  read(File.expand_path(args))
256
256
  end
@@ -112,7 +112,11 @@ class Reline::LineEditor
112
112
  else
113
113
  prompt = @prompt
114
114
  end
115
- if @prompt_proc
115
+ if !@is_multiline
116
+ mode_string = check_mode_string
117
+ prompt = mode_string + prompt if mode_string
118
+ [prompt] + [''] * (buffer.size - 1)
119
+ elsif @prompt_proc
116
120
  prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
117
121
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
118
122
  prompt_list = [prompt] if prompt_list.empty?
@@ -291,8 +295,8 @@ class Reline::LineEditor
291
295
  end
292
296
  end
293
297
 
294
- private def split_by_width(str, max_width)
295
- Reline::Unicode.split_by_width(str, max_width, @encoding)
298
+ private def split_by_width(str, max_width, offset: 0)
299
+ Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
296
300
  end
297
301
 
298
302
  def current_byte_pointer_cursor
@@ -366,7 +370,7 @@ class Reline::LineEditor
366
370
  @scroll_partial_screen
367
371
  end
368
372
 
369
- def wrapped_lines
373
+ def wrapped_prompt_and_input_lines
370
374
  with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
371
375
  prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
372
376
  cached_wraps = {}
@@ -377,9 +381,14 @@ class Reline::LineEditor
377
381
  end
378
382
 
379
383
  n.times.map do |i|
380
- prompt = prompts[i]
381
- line = lines[i]
382
- cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
384
+ prompt = prompts[i] || ''
385
+ line = lines[i] || ''
386
+ if (cached = cached_wraps[[prompt, line]])
387
+ next cached
388
+ end
389
+ *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
390
+ wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt)).first.compact
391
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
383
392
  end
384
393
  end
385
394
  end
@@ -405,8 +414,13 @@ class Reline::LineEditor
405
414
  @output.write "#{Reline::IOGate::RESET_COLOR}#{' ' * width}"
406
415
  else
407
416
  x, w, content = new_items[level]
408
- content = Reline::Unicode.take_range(content, base_x - x, width) unless x == base_x && w == width
409
- Reline::IOGate.move_cursor_column base_x
417
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
418
+ cover_end = new_levels[base_x + width] == level
419
+ pos = 0
420
+ unless x == base_x && w == width
421
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
422
+ end
423
+ Reline::IOGate.move_cursor_column x + pos
410
424
  @output.write "#{Reline::IOGate::RESET_COLOR}#{content}#{Reline::IOGate::RESET_COLOR}"
411
425
  end
412
426
  base_x += width
@@ -422,7 +436,7 @@ class Reline::LineEditor
422
436
  prompt_width = calculate_width(prompt_list[@line_index], true)
423
437
  line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
424
438
  wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
425
- wrapped_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
439
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
426
440
  wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
427
441
  [wrapped_cursor_x, wrapped_cursor_y]
428
442
  end
@@ -486,8 +500,9 @@ class Reline::LineEditor
486
500
  wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
487
501
 
488
502
  rendered_lines = @rendered_screen.lines
489
- new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
490
- [[0, Reline::Unicode.calculate_width(l, true), l]]
503
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
504
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
505
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
491
506
  end
492
507
  if @menu_info
493
508
  @menu_info.lines(screen_width).each do |item|
@@ -503,7 +518,8 @@ class Reline::LineEditor
503
518
  y_range.each do |row|
504
519
  next if row < 0 || row >= screen_height
505
520
  dialog_rows = new_lines[row] ||= []
506
- dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
521
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
522
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
507
523
  end
508
524
  end
509
525
 
@@ -688,13 +704,6 @@ class Reline::LineEditor
688
704
 
689
705
  DIALOG_DEFAULT_HEIGHT = 20
690
706
 
691
- private def padding_space_with_escape_sequences(str, width)
692
- padding_width = width - calculate_width(str, true)
693
- # padding_width should be only positive value. But macOS and Alacritty returns negative value.
694
- padding_width = 0 if padding_width < 0
695
- str + (' ' * padding_width)
696
- end
697
-
698
707
  private def dialog_range(dialog, dialog_y)
699
708
  x_range = dialog.column...dialog.column + dialog.width
700
709
  y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
@@ -767,7 +776,7 @@ class Reline::LineEditor
767
776
  dialog.contents = contents.map.with_index do |item, i|
768
777
  line_sgr = i == pointer ? enhanced_sgr : default_sgr
769
778
  str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
770
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
779
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
771
780
  colored_content = "#{line_sgr}#{str}"
772
781
  if scrollbar_pos
773
782
  if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
@@ -876,10 +885,12 @@ class Reline::LineEditor
876
885
  @completion_state = CompletionState::PERFECT_MATCH
877
886
  else
878
887
  @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
888
+ complete(list, true) if @config.show_all_if_ambiguous
879
889
  end
880
890
  @perfect_matched = completed
881
891
  else
882
892
  @completion_state = CompletionState::MENU
893
+ complete(list, true) if @config.show_all_if_ambiguous
883
894
  end
884
895
  if not just_show_list and target < completed
885
896
  @buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
@@ -1107,6 +1118,7 @@ class Reline::LineEditor
1107
1118
  end
1108
1119
  end
1109
1120
  if key.char.nil?
1121
+ process_insert(force: true)
1110
1122
  if @first_char
1111
1123
  @eof = true
1112
1124
  end
@@ -1220,7 +1232,7 @@ class Reline::LineEditor
1220
1232
  end
1221
1233
 
1222
1234
  def line()
1223
- current_line unless eof?
1235
+ @buffer_of_lines.join("\n") unless eof?
1224
1236
  end
1225
1237
 
1226
1238
  def current_line
@@ -1304,14 +1316,12 @@ class Reline::LineEditor
1304
1316
  end
1305
1317
  target = before
1306
1318
  end
1307
- if @is_multiline
1308
- lines = whole_lines
1309
- if @line_index > 0
1310
- preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1311
- end
1312
- if (lines.size - 1) > @line_index
1313
- postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1314
- end
1319
+ lines = whole_lines
1320
+ if @line_index > 0
1321
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1322
+ end
1323
+ if (lines.size - 1) > @line_index
1324
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1315
1325
  end
1316
1326
  [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
1317
1327
  end
@@ -1333,20 +1343,16 @@ class Reline::LineEditor
1333
1343
 
1334
1344
  def delete_text(start = nil, length = nil)
1335
1345
  if start.nil? and length.nil?
1336
- if @is_multiline
1337
- if @buffer_of_lines.size == 1
1338
- @buffer_of_lines[@line_index] = ''
1339
- @byte_pointer = 0
1340
- elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1341
- @buffer_of_lines.pop
1342
- @line_index -= 1
1343
- @byte_pointer = 0
1344
- elsif @line_index < (@buffer_of_lines.size - 1)
1345
- @buffer_of_lines.delete_at(@line_index)
1346
- @byte_pointer = 0
1347
- end
1348
- else
1349
- set_current_line('', 0)
1346
+ if @buffer_of_lines.size == 1
1347
+ @buffer_of_lines[@line_index] = ''
1348
+ @byte_pointer = 0
1349
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1350
+ @buffer_of_lines.pop
1351
+ @line_index -= 1
1352
+ @byte_pointer = 0
1353
+ elsif @line_index < (@buffer_of_lines.size - 1)
1354
+ @buffer_of_lines.delete_at(@line_index)
1355
+ @byte_pointer = 0
1350
1356
  end
1351
1357
  elsif not start.nil? and not length.nil?
1352
1358
  if current_line
@@ -1502,7 +1508,7 @@ class Reline::LineEditor
1502
1508
  byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1503
1509
  if (@byte_pointer < current_line.bytesize)
1504
1510
  @byte_pointer += byte_size
1505
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1511
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1506
1512
  @byte_pointer = 0
1507
1513
  @line_index += 1
1508
1514
  end
@@ -1515,7 +1521,7 @@ class Reline::LineEditor
1515
1521
  if @byte_pointer > 0
1516
1522
  byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1517
1523
  @byte_pointer -= byte_size
1518
- elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1524
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1519
1525
  @line_index -= 1
1520
1526
  @byte_pointer = current_line.bytesize
1521
1527
  end
@@ -1535,139 +1541,99 @@ class Reline::LineEditor
1535
1541
  alias_method :vi_zero, :ed_move_to_beg
1536
1542
 
1537
1543
  private def ed_move_to_end(key)
1538
- @byte_pointer = 0
1539
- while @byte_pointer < current_line.bytesize
1540
- byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1541
- @byte_pointer += byte_size
1542
- end
1544
+ @byte_pointer = current_line.bytesize
1543
1545
  end
1544
1546
  alias_method :end_of_line, :ed_move_to_end
1545
1547
 
1546
- private def generate_searcher
1547
- Fiber.new do |first_key|
1548
- prev_search_key = first_key
1549
- search_word = String.new(encoding: @encoding)
1550
- multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1551
- last_hit = nil
1552
- case first_key
1553
- when "\C-r".ord
1554
- prompt_name = 'reverse-i-search'
1555
- when "\C-s".ord
1556
- prompt_name = 'i-search'
1548
+ private def generate_searcher(search_key)
1549
+ search_word = String.new(encoding: @encoding)
1550
+ multibyte_buf = String.new(encoding: 'ASCII-8BIT')
1551
+ hit_pointer = nil
1552
+ lambda do |key|
1553
+ search_again = false
1554
+ case key
1555
+ when "\C-h".ord, "\C-?".ord
1556
+ grapheme_clusters = search_word.grapheme_clusters
1557
+ if grapheme_clusters.size > 0
1558
+ grapheme_clusters.pop
1559
+ search_word = grapheme_clusters.join
1560
+ end
1561
+ when "\C-r".ord, "\C-s".ord
1562
+ search_again = true if search_key == key
1563
+ search_key = key
1564
+ else
1565
+ multibyte_buf << key
1566
+ if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1567
+ search_word << multibyte_buf.dup.force_encoding(@encoding)
1568
+ multibyte_buf.clear
1569
+ end
1557
1570
  end
1558
- loop do
1559
- key = Fiber.yield(search_word)
1560
- search_again = false
1561
- case key
1562
- when -1 # determined
1563
- Reline.last_incremental_search = search_word
1564
- break
1565
- when "\C-h".ord, "\C-?".ord
1566
- grapheme_clusters = search_word.grapheme_clusters
1567
- if grapheme_clusters.size > 0
1568
- grapheme_clusters.pop
1569
- search_word = grapheme_clusters.join
1570
- end
1571
- when "\C-r".ord, "\C-s".ord
1572
- search_again = true if prev_search_key == key
1573
- prev_search_key = key
1574
- else
1575
- multibyte_buf << key
1576
- if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1577
- search_word << multibyte_buf.dup.force_encoding(@encoding)
1578
- multibyte_buf.clear
1571
+ hit = nil
1572
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1573
+ hit_pointer = Reline::HISTORY.size
1574
+ hit = @line_backup_in_history
1575
+ else
1576
+ if search_again
1577
+ if search_word.empty? and Reline.last_incremental_search
1578
+ search_word = Reline.last_incremental_search
1579
1579
  end
1580
- end
1581
- hit = nil
1582
- if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1583
- @history_pointer = nil
1584
- hit = @line_backup_in_history
1585
- else
1586
- if search_again
1587
- if search_word.empty? and Reline.last_incremental_search
1588
- search_word = Reline.last_incremental_search
1589
- end
1590
- if @history_pointer
1591
- case prev_search_key
1592
- when "\C-r".ord
1593
- history_pointer_base = 0
1594
- history = Reline::HISTORY[0..(@history_pointer - 1)]
1595
- when "\C-s".ord
1596
- history_pointer_base = @history_pointer + 1
1597
- history = Reline::HISTORY[(@history_pointer + 1)..-1]
1598
- end
1599
- else
1600
- history_pointer_base = 0
1601
- history = Reline::HISTORY
1602
- end
1603
- elsif @history_pointer
1604
- case prev_search_key
1580
+ if @history_pointer
1581
+ case search_key
1605
1582
  when "\C-r".ord
1606
1583
  history_pointer_base = 0
1607
- history = Reline::HISTORY[0..@history_pointer]
1584
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1608
1585
  when "\C-s".ord
1609
- history_pointer_base = @history_pointer
1610
- history = Reline::HISTORY[@history_pointer..-1]
1586
+ history_pointer_base = @history_pointer + 1
1587
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1611
1588
  end
1612
1589
  else
1613
1590
  history_pointer_base = 0
1614
1591
  history = Reline::HISTORY
1615
1592
  end
1616
- case prev_search_key
1593
+ elsif @history_pointer
1594
+ case search_key
1617
1595
  when "\C-r".ord
1618
- hit_index = history.rindex { |item|
1619
- item.include?(search_word)
1620
- }
1596
+ history_pointer_base = 0
1597
+ history = Reline::HISTORY[0..@history_pointer]
1621
1598
  when "\C-s".ord
1622
- hit_index = history.index { |item|
1623
- item.include?(search_word)
1624
- }
1625
- end
1626
- if hit_index
1627
- @history_pointer = history_pointer_base + hit_index
1628
- hit = Reline::HISTORY[@history_pointer]
1599
+ history_pointer_base = @history_pointer
1600
+ history = Reline::HISTORY[@history_pointer..-1]
1629
1601
  end
1602
+ else
1603
+ history_pointer_base = 0
1604
+ history = Reline::HISTORY
1630
1605
  end
1631
- case prev_search_key
1606
+ case search_key
1632
1607
  when "\C-r".ord
1633
- prompt_name = 'reverse-i-search'
1608
+ hit_index = history.rindex { |item|
1609
+ item.include?(search_word)
1610
+ }
1634
1611
  when "\C-s".ord
1635
- prompt_name = 'i-search'
1612
+ hit_index = history.index { |item|
1613
+ item.include?(search_word)
1614
+ }
1636
1615
  end
1637
- if hit
1638
- if @is_multiline
1639
- @buffer_of_lines = hit.split("\n")
1640
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1641
- @line_index = @buffer_of_lines.size - 1
1642
- @byte_pointer = current_line.bytesize
1643
- @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1644
- else
1645
- @buffer_of_lines = [hit]
1646
- @byte_pointer = hit.bytesize
1647
- @searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
1648
- end
1649
- last_hit = hit
1650
- else
1651
- if @is_multiline
1652
- @searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
1653
- else
1654
- @searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
1655
- end
1616
+ if hit_index
1617
+ hit_pointer = history_pointer_base + hit_index
1618
+ hit = Reline::HISTORY[hit_pointer]
1656
1619
  end
1657
1620
  end
1621
+ case search_key
1622
+ when "\C-r".ord
1623
+ prompt_name = 'reverse-i-search'
1624
+ when "\C-s".ord
1625
+ prompt_name = 'i-search'
1626
+ end
1627
+ prompt_name = "failed #{prompt_name}" unless hit
1628
+ [search_word, prompt_name, hit_pointer]
1658
1629
  end
1659
1630
  end
1660
1631
 
1661
1632
  private def incremental_search_history(key)
1662
1633
  unless @history_pointer
1663
- if @is_multiline
1664
- @line_backup_in_history = whole_buffer
1665
- else
1666
- @line_backup_in_history = current_line
1667
- end
1634
+ @line_backup_in_history = whole_buffer
1668
1635
  end
1669
- searcher = generate_searcher
1670
- searcher.resume(key)
1636
+ searcher = generate_searcher(key)
1671
1637
  @searching_prompt = "(reverse-i-search)`': "
1672
1638
  termination_keys = ["\C-j".ord]
1673
1639
  termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@@ -1679,53 +1645,41 @@ class Reline::LineEditor
1679
1645
  else
1680
1646
  buffer = @line_backup_in_history
1681
1647
  end
1682
- if @is_multiline
1683
- @buffer_of_lines = buffer.split("\n")
1684
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1685
- @line_index = @buffer_of_lines.size - 1
1686
- else
1687
- @buffer_of_lines = [buffer]
1688
- end
1648
+ @buffer_of_lines = buffer.split("\n")
1649
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1650
+ @line_index = @buffer_of_lines.size - 1
1689
1651
  @searching_prompt = nil
1690
1652
  @waiting_proc = nil
1691
1653
  @byte_pointer = 0
1692
- searcher.resume(-1)
1693
1654
  when "\C-g".ord
1694
- if @is_multiline
1695
- @buffer_of_lines = @line_backup_in_history.split("\n")
1696
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1697
- @line_index = @buffer_of_lines.size - 1
1698
- else
1699
- @buffer_of_lines = [@line_backup_in_history]
1700
- end
1701
- @history_pointer = nil
1655
+ @buffer_of_lines = @line_backup_in_history.split("\n")
1656
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1657
+ @line_index = @buffer_of_lines.size - 1
1658
+ move_history(nil, line: :end, cursor: :end, save_buffer: false)
1702
1659
  @searching_prompt = nil
1703
1660
  @waiting_proc = nil
1704
- @line_backup_in_history = nil
1705
1661
  @byte_pointer = 0
1706
1662
  else
1707
1663
  chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1708
1664
  if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
1709
- searcher.resume(k)
1665
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1666
+ Reline.last_incremental_search = search_word
1667
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1668
+ @searching_prompt += ': ' unless @is_multiline
1669
+ move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
1710
1670
  else
1711
1671
  if @history_pointer
1712
1672
  line = Reline::HISTORY[@history_pointer]
1713
1673
  else
1714
1674
  line = @line_backup_in_history
1715
1675
  end
1716
- if @is_multiline
1717
- @line_backup_in_history = whole_buffer
1718
- @buffer_of_lines = line.split("\n")
1719
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1720
- @line_index = @buffer_of_lines.size - 1
1721
- else
1722
- @line_backup_in_history = current_line
1723
- @buffer_of_lines = [line]
1724
- end
1676
+ @line_backup_in_history = whole_buffer
1677
+ @buffer_of_lines = line.split("\n")
1678
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1679
+ @line_index = @buffer_of_lines.size - 1
1725
1680
  @searching_prompt = nil
1726
1681
  @waiting_proc = nil
1727
1682
  @byte_pointer = 0
1728
- searcher.resume(-1)
1729
1683
  end
1730
1684
  end
1731
1685
  }
@@ -1741,191 +1695,95 @@ class Reline::LineEditor
1741
1695
  end
1742
1696
  alias_method :forward_search_history, :vi_search_next
1743
1697
 
1744
- private def ed_search_prev_history(key, arg: 1)
1745
- history = nil
1746
- h_pointer = nil
1747
- line_no = nil
1748
- substr = current_line.slice(0, @byte_pointer)
1749
- if @history_pointer.nil?
1750
- return if not current_line.empty? and substr.empty?
1751
- history = Reline::HISTORY
1752
- elsif @history_pointer.zero?
1753
- history = nil
1754
- h_pointer = nil
1755
- else
1756
- history = Reline::HISTORY.slice(0, @history_pointer)
1757
- end
1758
- return if history.nil?
1759
- if @is_multiline
1760
- h_pointer = history.rindex { |h|
1761
- h.split("\n").each_with_index { |l, i|
1762
- if l.start_with?(substr)
1763
- line_no = i
1764
- break
1765
- end
1766
- }
1767
- not line_no.nil?
1768
- }
1769
- else
1770
- h_pointer = history.rindex { |l|
1771
- l.start_with?(substr)
1772
- }
1773
- end
1774
- return if h_pointer.nil?
1775
- @history_pointer = h_pointer
1776
- cursor = current_byte_pointer_cursor
1777
- if @is_multiline
1778
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1779
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1780
- @line_index = line_no
1781
- calculate_nearest_cursor(cursor)
1782
- else
1783
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1784
- calculate_nearest_cursor(cursor)
1698
+ private def search_history(prefix, pointer_range)
1699
+ pointer_range.each do |pointer|
1700
+ lines = Reline::HISTORY[pointer].split("\n")
1701
+ lines.each_with_index do |line, index|
1702
+ return [pointer, index] if line.start_with?(prefix)
1703
+ end
1785
1704
  end
1705
+ nil
1706
+ end
1707
+
1708
+ private def ed_search_prev_history(key, arg: 1)
1709
+ substr = current_line.byteslice(0, @byte_pointer)
1710
+ return if @history_pointer == 0
1711
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1712
+
1713
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
1714
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
1715
+ return unless h_pointer
1716
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1786
1717
  arg -= 1
1787
1718
  ed_search_prev_history(key, arg: arg) if arg > 0
1788
1719
  end
1789
1720
  alias_method :history_search_backward, :ed_search_prev_history
1790
1721
 
1791
1722
  private def ed_search_next_history(key, arg: 1)
1792
- substr = current_line.slice(0, @byte_pointer)
1793
- if @history_pointer.nil?
1794
- return
1795
- elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
1796
- return
1797
- end
1798
- history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
1799
- h_pointer = nil
1800
- line_no = nil
1801
- if @is_multiline
1802
- h_pointer = history.index { |h|
1803
- h.split("\n").each_with_index { |l, i|
1804
- if l.start_with?(substr)
1805
- line_no = i
1806
- break
1807
- end
1808
- }
1809
- not line_no.nil?
1810
- }
1811
- else
1812
- h_pointer = history.index { |l|
1813
- l.start_with?(substr)
1814
- }
1815
- end
1816
- h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
1723
+ substr = current_line.byteslice(0, @byte_pointer)
1724
+ return if @history_pointer.nil?
1725
+
1726
+ history_range = @history_pointer + 1...Reline::HISTORY.size
1727
+ h_pointer, line_index = search_history(substr, history_range)
1817
1728
  return if h_pointer.nil? and not substr.empty?
1818
- @history_pointer = h_pointer
1819
- if @is_multiline
1820
- if @history_pointer.nil? and substr.empty?
1821
- @buffer_of_lines = []
1822
- @line_index = 0
1823
- @byte_pointer = 0
1824
- else
1825
- cursor = current_byte_pointer_cursor
1826
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1827
- @line_index = line_no
1828
- calculate_nearest_cursor(cursor)
1829
- end
1830
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1831
- else
1832
- if @history_pointer.nil? and substr.empty?
1833
- set_current_line('', 0)
1834
- else
1835
- set_current_line(Reline::HISTORY[@history_pointer])
1836
- end
1837
- end
1729
+
1730
+ move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer)
1838
1731
  arg -= 1
1839
1732
  ed_search_next_history(key, arg: arg) if arg > 0
1840
1733
  end
1841
1734
  alias_method :history_search_forward, :ed_search_next_history
1842
1735
 
1736
+ private def move_history(history_pointer, line:, cursor:, save_buffer: true)
1737
+ history_pointer ||= Reline::HISTORY.size
1738
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1739
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
1740
+ if old_history_pointer == Reline::HISTORY.size
1741
+ @line_backup_in_history = save_buffer ? whole_buffer : ''
1742
+ else
1743
+ Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
1744
+ end
1745
+ if history_pointer == Reline::HISTORY.size
1746
+ buf = @line_backup_in_history
1747
+ @history_pointer = @line_backup_in_history = nil
1748
+ else
1749
+ buf = Reline::HISTORY[history_pointer]
1750
+ @history_pointer = history_pointer
1751
+ end
1752
+ @buffer_of_lines = buf.split("\n")
1753
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1754
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1755
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1756
+ end
1757
+
1843
1758
  private def ed_prev_history(key, arg: 1)
1844
- if @is_multiline and @line_index > 0
1759
+ if @line_index > 0
1845
1760
  cursor = current_byte_pointer_cursor
1846
1761
  @line_index -= 1
1847
1762
  calculate_nearest_cursor(cursor)
1848
1763
  return
1849
1764
  end
1850
- if Reline::HISTORY.empty?
1851
- return
1852
- end
1853
- if @history_pointer.nil?
1854
- @history_pointer = Reline::HISTORY.size - 1
1855
- cursor = current_byte_pointer_cursor
1856
- if @is_multiline
1857
- @line_backup_in_history = whole_buffer
1858
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1859
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1860
- @line_index = @buffer_of_lines.size - 1
1861
- calculate_nearest_cursor(cursor)
1862
- else
1863
- @line_backup_in_history = whole_buffer
1864
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1865
- calculate_nearest_cursor(cursor)
1866
- end
1867
- elsif @history_pointer.zero?
1868
- return
1869
- else
1870
- if @is_multiline
1871
- Reline::HISTORY[@history_pointer] = whole_buffer
1872
- @history_pointer -= 1
1873
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1874
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1875
- @line_index = @buffer_of_lines.size - 1
1876
- else
1877
- Reline::HISTORY[@history_pointer] = whole_buffer
1878
- @history_pointer -= 1
1879
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1880
- end
1881
- end
1882
- if @config.editing_mode_is?(:emacs, :vi_insert)
1883
- @byte_pointer = current_line.bytesize
1884
- elsif @config.editing_mode_is?(:vi_command)
1885
- @byte_pointer = 0
1886
- end
1765
+ move_history(
1766
+ (@history_pointer || Reline::HISTORY.size) - 1,
1767
+ line: :end,
1768
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1769
+ )
1887
1770
  arg -= 1
1888
1771
  ed_prev_history(key, arg: arg) if arg > 0
1889
1772
  end
1890
1773
  alias_method :previous_history, :ed_prev_history
1891
1774
 
1892
1775
  private def ed_next_history(key, arg: 1)
1893
- if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
1776
+ if @line_index < (@buffer_of_lines.size - 1)
1894
1777
  cursor = current_byte_pointer_cursor
1895
1778
  @line_index += 1
1896
1779
  calculate_nearest_cursor(cursor)
1897
1780
  return
1898
1781
  end
1899
- if @history_pointer.nil?
1900
- return
1901
- elsif @history_pointer == (Reline::HISTORY.size - 1)
1902
- if @is_multiline
1903
- @history_pointer = nil
1904
- @buffer_of_lines = @line_backup_in_history.split("\n")
1905
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1906
- @line_index = 0
1907
- else
1908
- @history_pointer = nil
1909
- @buffer_of_lines = [@line_backup_in_history]
1910
- end
1911
- else
1912
- if @is_multiline
1913
- Reline::HISTORY[@history_pointer] = whole_buffer
1914
- @history_pointer += 1
1915
- @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
1916
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1917
- @line_index = 0
1918
- else
1919
- Reline::HISTORY[@history_pointer] = whole_buffer
1920
- @history_pointer += 1
1921
- @buffer_of_lines = [Reline::HISTORY[@history_pointer]]
1922
- end
1923
- end
1924
- if @config.editing_mode_is?(:emacs, :vi_insert)
1925
- @byte_pointer = current_line.bytesize
1926
- elsif @config.editing_mode_is?(:vi_command)
1927
- @byte_pointer = 0
1928
- end
1782
+ move_history(
1783
+ (@history_pointer || Reline::HISTORY.size) + 1,
1784
+ line: :start,
1785
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1786
+ )
1929
1787
  arg -= 1
1930
1788
  ed_next_history(key, arg: arg) if arg > 0
1931
1789
  end
@@ -1956,17 +1814,13 @@ class Reline::LineEditor
1956
1814
  end
1957
1815
  end
1958
1816
  else
1959
- if @history_pointer
1960
- Reline::HISTORY[@history_pointer] = whole_buffer
1961
- @history_pointer = nil
1962
- end
1963
1817
  finish
1964
1818
  end
1965
1819
  end
1966
1820
 
1967
1821
  private def em_delete_prev_char(key, arg: 1)
1968
1822
  arg.times do
1969
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
1823
+ if @byte_pointer == 0 and @line_index > 0
1970
1824
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1971
1825
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1972
1826
  @line_index -= 1
@@ -1990,7 +1844,7 @@ class Reline::LineEditor
1990
1844
  line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1991
1845
  set_current_line(line, line.bytesize)
1992
1846
  @kill_ring.append(deleted)
1993
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1847
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1994
1848
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1995
1849
  end
1996
1850
  end
@@ -2030,7 +1884,7 @@ class Reline::LineEditor
2030
1884
  alias_method :kill_whole_line, :em_kill_line
2031
1885
 
2032
1886
  private def em_delete(key)
2033
- if current_line.empty? and (not @is_multiline or @buffer_of_lines.size == 1) and key == "\C-d".ord
1887
+ if current_line.empty? and @buffer_of_lines.size == 1 and key == "\C-d".ord
2034
1888
  @eof = true
2035
1889
  finish
2036
1890
  elsif @byte_pointer < current_line.bytesize
@@ -2038,7 +1892,7 @@ class Reline::LineEditor
2038
1892
  mbchar = splitted_last.grapheme_clusters.first
2039
1893
  line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
2040
1894
  set_current_line(line)
2041
- elsif @is_multiline and @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1895
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
2042
1896
  set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
2043
1897
  end
2044
1898
  end
@@ -2281,7 +2135,7 @@ class Reline::LineEditor
2281
2135
  end
2282
2136
 
2283
2137
  private def vi_delete_prev_char(key)
2284
- if @is_multiline and @byte_pointer == 0 and @line_index > 0
2138
+ if @byte_pointer == 0 and @line_index > 0
2285
2139
  @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2286
2140
  @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2287
2141
  @line_index -= 1
@@ -2378,7 +2232,7 @@ class Reline::LineEditor
2378
2232
  end
2379
2233
 
2380
2234
  private def vi_list_or_eof(key)
2381
- if (not @is_multiline and current_line.empty?) or (@is_multiline and current_line.empty? and @buffer_of_lines.size == 1)
2235
+ if current_line.empty? and @buffer_of_lines.size == 1
2382
2236
  set_current_line('', 0)
2383
2237
  @eof = true
2384
2238
  finish
@@ -2409,36 +2263,18 @@ class Reline::LineEditor
2409
2263
  if Reline::HISTORY.empty?
2410
2264
  return
2411
2265
  end
2412
- if @history_pointer.nil?
2413
- @history_pointer = 0
2414
- @line_backup_in_history = current_line
2415
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2416
- elsif @history_pointer.zero?
2417
- return
2418
- else
2419
- Reline::HISTORY[@history_pointer] = current_line
2420
- @history_pointer = 0
2421
- set_current_line(Reline::HISTORY[@history_pointer], 0)
2422
- end
2266
+ move_history(0, line: :start, cursor: :start)
2423
2267
  end
2424
2268
 
2425
2269
  private def vi_histedit(key)
2426
2270
  path = Tempfile.open { |fp|
2427
- if @is_multiline
2428
- fp.write whole_lines.join("\n")
2429
- else
2430
- fp.write current_line
2431
- end
2271
+ fp.write whole_lines.join("\n")
2432
2272
  fp.path
2433
2273
  }
2434
2274
  system("#{ENV['EDITOR']} #{path}")
2435
- if @is_multiline
2436
- @buffer_of_lines = File.read(path).split("\n")
2437
- @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2438
- @line_index = 0
2439
- else
2440
- @buffer_of_lines = File.read(path).split("\n")
2441
- end
2275
+ @buffer_of_lines = File.read(path).split("\n")
2276
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
2277
+ @line_index = 0
2442
2278
  finish
2443
2279
  end
2444
2280
 
@@ -2610,7 +2446,7 @@ class Reline::LineEditor
2610
2446
  end
2611
2447
 
2612
2448
  private def vi_join_lines(key, arg: 1)
2613
- if @is_multiline and @buffer_of_lines.size > @line_index + 1
2449
+ if @buffer_of_lines.size > @line_index + 1
2614
2450
  next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2615
2451
  set_current_line(current_line + ' ' + next_line, current_line.bytesize)
2616
2452
  end
@@ -128,10 +128,10 @@ class Reline::Unicode
128
128
  end
129
129
  end
130
130
 
131
- def self.split_by_width(str, max_width, encoding = str.encoding)
131
+ def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
132
132
  lines = [String.new(encoding: encoding)]
133
133
  height = 1
134
- width = 0
134
+ width = offset
135
135
  rest = str.encode(Encoding::UTF_8)
136
136
  in_zero_width = false
137
137
  seq = String.new(encoding: encoding)
@@ -145,7 +145,13 @@ class Reline::Unicode
145
145
  lines.last << NON_PRINTING_END
146
146
  when csi
147
147
  lines.last << csi
148
- seq << csi
148
+ unless in_zero_width
149
+ if csi == -"\e[m" || csi == -"\e[0m"
150
+ seq.clear
151
+ else
152
+ seq << csi
153
+ end
154
+ end
149
155
  when osc
150
156
  lines.last << osc
151
157
  seq << osc
@@ -173,32 +179,78 @@ class Reline::Unicode
173
179
 
174
180
  # Take a chunk of a String cut by width with escape sequences.
175
181
  def self.take_range(str, start_col, max_width)
182
+ take_mbchar_range(str, start_col, max_width).first
183
+ end
184
+
185
+ def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false)
176
186
  chunk = String.new(encoding: str.encoding)
187
+
188
+ end_col = start_col + width
177
189
  total_width = 0
178
190
  rest = str.encode(Encoding::UTF_8)
179
191
  in_zero_width = false
192
+ chunk_start_col = nil
193
+ chunk_end_col = nil
194
+ has_csi = false
180
195
  rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
181
196
  case
182
197
  when non_printing_start
183
198
  in_zero_width = true
199
+ chunk << NON_PRINTING_START
184
200
  when non_printing_end
185
201
  in_zero_width = false
202
+ chunk << NON_PRINTING_END
186
203
  when csi
204
+ has_csi = true
187
205
  chunk << csi
188
206
  when osc
189
207
  chunk << osc
190
208
  when gc
191
209
  if in_zero_width
192
210
  chunk << gc
211
+ next
212
+ end
213
+
214
+ mbchar_width = get_mbchar_width(gc)
215
+ prev_width = total_width
216
+ total_width += mbchar_width
217
+
218
+ if (cover_begin || padding ? total_width <= start_col : prev_width < start_col)
219
+ # Current character haven't reached start_col yet
220
+ next
221
+ elsif padding && !cover_begin && prev_width < start_col && start_col < total_width
222
+ # Add preceding padding. This padding might have background color.
223
+ chunk << ' '
224
+ chunk_start_col ||= start_col
225
+ chunk_end_col = total_width
226
+ next
227
+ elsif (cover_end ? prev_width < end_col : total_width <= end_col)
228
+ # Current character is in the range
229
+ chunk << gc
230
+ chunk_start_col ||= prev_width
231
+ chunk_end_col = total_width
232
+ break if total_width >= end_col
193
233
  else
194
- mbchar_width = get_mbchar_width(gc)
195
- total_width += mbchar_width
196
- break if (start_col + max_width) < total_width
197
- chunk << gc if start_col < total_width
234
+ # Current character exceeds end_col
235
+ if padding && end_col < total_width
236
+ # Add succeeding padding. This padding might have background color.
237
+ chunk << ' '
238
+ chunk_start_col ||= prev_width
239
+ chunk_end_col = end_col
240
+ end
241
+ break
198
242
  end
199
243
  end
200
244
  end
201
- chunk
245
+ chunk_start_col ||= start_col
246
+ chunk_end_col ||= start_col
247
+ if padding && chunk_end_col < end_col
248
+ # Append padding. This padding should not include background color.
249
+ chunk << "\e[0m" if has_csi
250
+ chunk << ' ' * (end_col - chunk_end_col)
251
+ chunk_end_col = end_col
252
+ end
253
+ [chunk, chunk_start_col, chunk_end_col - chunk_start_col]
202
254
  end
203
255
 
204
256
  def self.get_next_mbchar_size(line, byte_pointer)
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.5.2'
2
+ VERSION = '0.5.4'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-16 00:00:00.000000000 Z
11
+ date: 2024-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console