Skip to content

Commit 57e2a7d

Browse files
committed
Re-implement prev_spaces feature for pasted code
1 parent 1159c13 commit 57e2a7d

File tree

2 files changed

+93
-6
lines changed

2 files changed

+93
-6
lines changed

lib/irb/ruby-lex.rb

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ def calc_nesting_depth(opens)
370370
indent_level = 0
371371
end
372372
end
373-
when :on_tstring_beg, :on_regexp_beg, :on_symbeg
373+
when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick
374374
# can be indented if t.tok starts with `%`
375375
when :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_embexpr_beg
376376
# can be indented but not indented in current implementation
@@ -390,6 +390,26 @@ def free_indent_token?(token)
390390
FREE_INDENT_TOKENS.include?(token&.event)
391391
end
392392

393+
# Calculates the difference of pasted code's indent and indent calculated from tokens
394+
def indent_difference(lines, line_results, line_index)
395+
loop do
396+
_tokens, prev_opens, _next_opens, min_depth = line_results[line_index]
397+
open_token = prev_opens.last
398+
if !open_token || (open_token.event != :on_heredoc_beg && !free_indent_token?(open_token))
399+
# If the leading whitespace is an indent, return the difference
400+
indent_level, _nesting_level = calc_nesting_depth(prev_opens.take(min_depth))
401+
calculated_indent = 2 * indent_level
402+
actual_indent = lines[line_index][/^ */].size
403+
return actual_indent - calculated_indent
404+
elsif open_token.event == :on_heredoc_beg && open_token.tok.match?(/^<<[^-~]/)
405+
return 0
406+
end
407+
# If the leading whitespace is not an indent but part of a multiline token
408+
# Calculate base_indent of the multiline token's beginning line
409+
line_index = open_token.pos[0] - 1
410+
end
411+
end
412+
393413
def process_indent_level(tokens, lines, line_index, is_newline)
394414
line_results = IRB::NestingParser.parse_by_line(tokens)
395415
result = line_results[line_index]
@@ -411,10 +431,20 @@ def process_indent_level(tokens, lines, line_index, is_newline)
411431
prev_open_token = prev_opens.last
412432
next_open_token = next_opens.last
413433

434+
# Calculates base indent for pasted code on the line where prev_open_token is located
435+
# irb(main):001:1* if a # base_indent is 2, indent calculated from tokens is 0
436+
# irb(main):002:1* if b # base_indent is 6, indent calculated from tokens is 2
437+
# irb(main):003:0> c # base_indent is 6, indent calculated from tokens is 4
438+
if prev_open_token
439+
base_indent = [0, indent_difference(lines, line_results, prev_open_token.pos[0] - 1)].max
440+
else
441+
base_indent = 0
442+
end
443+
414444
if free_indent_token?(prev_open_token)
415445
if is_newline && prev_open_token.pos[0] == line_index
416446
# First newline inside free-indent token
417-
indent
447+
base_indent + indent
418448
else
419449
# Accept any number of indent inside free-indent token
420450
preserve_indent
@@ -432,21 +462,21 @@ def process_indent_level(tokens, lines, line_index, is_newline)
432462
if prev_opens.size <= next_opens.size
433463
if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token
434464
# First line in heredoc
435-
indent
465+
tok.match?(/^<<[-~]/) ? base_indent + indent : indent
436466
elsif tok.match?(/^<<~/)
437467
# Accept extra indent spaces inside `<<~` heredoc
438-
[indent, preserve_indent].max
468+
[base_indent + indent, preserve_indent].max
439469
else
440470
# Accept any number of indent inside other heredoc
441471
preserve_indent
442472
end
443473
else
444474
# Heredoc close
445475
prev_line_indent_level, _prev_line_nesting_level = calc_nesting_depth(prev_opens)
446-
tok.match?(/^<<[~-]/) ? 2 * (prev_line_indent_level - 1) : 0
476+
tok.match?(/^<<[~-]/) ? base_indent + 2 * (prev_line_indent_level - 1) : 0
447477
end
448478
else
449-
indent
479+
base_indent + indent
450480
end
451481
end
452482

test/irb/test_ruby_lex.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,63 @@ def dynamic_prompt(&block)
698698
end
699699
end
700700

701+
def test_pasted_code_keep_base_indent_spaces
702+
input_with_correct_indents = [
703+
Row.new(%q( def foo), 0, 6, 1),
704+
Row.new(%q( if bar), 6, 10, 2),
705+
Row.new(%q( [1), 10, 12, 3),
706+
Row.new(%q( ]+[["a), 10, 14, 4),
707+
Row.new(%q(b" + `c), 0, 14, 4),
708+
Row.new(%q(d` + /e), 0, 14, 4),
709+
Row.new(%q(f/ + :"g), 0, 14, 4),
710+
Row.new(%q(h".tap do), 0, 16, 5),
711+
Row.new(%q( 1), 16, 16, 5),
712+
Row.new(%q( end), 14, 14, 4),
713+
Row.new(%q( ]), 12, 12, 3),
714+
Row.new(%q( ]), 10, 10, 2),
715+
Row.new(%q( end), 8, 6, 1),
716+
Row.new(%q( end), 4, 0, 0),
717+
]
718+
lines = []
719+
input_with_correct_indents.each do |row|
720+
lines << row.content
721+
assert_row_indenting(lines, row)
722+
assert_nesting_level(lines, row.nesting_level)
723+
end
724+
end
725+
726+
def test_pasted_code_keep_base_indent_spaces_with_heredoc
727+
input_with_correct_indents = [
728+
Row.new(%q( def foo), 0, 6, 1),
729+
Row.new(%q( if bar), 6, 10, 2),
730+
Row.new(%q( [1), 10, 12, 3),
731+
Row.new(%q( ]+[["a), 10, 14, 4),
732+
Row.new(%q(b" + <<~A + <<-B + <<C), 0, 16, 4),
733+
Row.new(%q( a#{), 16, 16, 4),
734+
Row.new(%q( 1), 16, 16, 4),
735+
Row.new(%q( }), 16, 16, 4),
736+
Row.new(%q( A), 14, 16, 4),
737+
Row.new(%q( b#{), 16, 16, 4),
738+
Row.new(%q( 1), 16, 16, 4),
739+
Row.new(%q( }), 16, 16, 4),
740+
Row.new(%q( B), 14, 0, 4),
741+
Row.new(%q(c#{), 0, 0, 4),
742+
Row.new(%q(1), 0, 0, 4),
743+
Row.new(%q(}), 0, 0, 4),
744+
Row.new(%q(C), 0, 14, 4),
745+
Row.new(%q( ]), 12, 12, 3),
746+
Row.new(%q( ]), 10, 10, 2),
747+
Row.new(%q( end), 8, 6, 1),
748+
Row.new(%q( end), 4, 0, 0),
749+
]
750+
lines = []
751+
input_with_correct_indents.each do |row|
752+
lines << row.content
753+
assert_row_indenting(lines, row)
754+
assert_nesting_level(lines, row.nesting_level)
755+
end
756+
end
757+
701758
def assert_dynamic_prompt(lines, expected_prompt_list)
702759
pend if RUBY_ENGINE == 'truffleruby'
703760
context = build_context

0 commit comments

Comments
 (0)