reline 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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