Skip to content

Commit 1159c13

Browse files
authored
Improve indentation: bugfix, heredoc, embdoc, strings (#515)
* Implement heredoc embdoc and string indentation with bugfix * Fix test_ruby_lex's indentation value * Add embdoc indent test * Add workaround for lines==[nil] passed to auto_indent when exit IRB with CTRL+d
1 parent 21c400e commit 1159c13

File tree

3 files changed

+222
-177
lines changed

3 files changed

+222
-177
lines changed

lib/irb/ruby-lex.rb

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,12 @@ def configure_io(io)
9393

9494
if @io.respond_to?(:auto_indent) and @context.auto_indent_mode
9595
@io.auto_indent do |lines, line_index, byte_pointer, is_newline|
96-
if is_newline
97-
tokens = self.class.ripper_lex_without_warning(lines[0..line_index].join("\n"), context: @context)
98-
process_indent_level(tokens, lines)
99-
else
100-
code = line_index.zero? ? '' : lines[0..(line_index - 1)].map{ |l| l + "\n" }.join
101-
last_line = lines[line_index]&.byteslice(0, byte_pointer)
102-
code += last_line if last_line
103-
tokens = self.class.ripper_lex_without_warning(code, context: @context)
104-
check_corresponding_token_depth(tokens, lines, line_index)
105-
end
96+
next nil if lines == [nil] # Workaround for exit IRB with CTRL+d
97+
next nil if !is_newline && lines[line_index]&.byteslice(0, byte_pointer)&.match?(/\A\s*\z/)
98+
99+
code = lines[0..line_index].map { |l| "#{l}\n" }.join
100+
tokens = self.class.ripper_lex_without_warning(code, context: @context)
101+
process_indent_level(tokens, lines, line_index, is_newline)
106102
end
107103
end
108104
end
@@ -364,10 +360,16 @@ def check_code_block(code, tokens)
364360
def calc_nesting_depth(opens)
365361
indent_level = 0
366362
nesting_level = 0
367-
opens.each do |t|
363+
opens.each_with_index do |t, index|
368364
case t.event
369365
when :on_heredoc_beg
370-
# TODO: indent heredoc
366+
if opens[index + 1]&.event != :on_heredoc_beg
367+
if t.tok.match?(/^<<[~-]/)
368+
indent_level += 1
369+
else
370+
indent_level = 0
371+
end
372+
end
371373
when :on_tstring_beg, :on_regexp_beg, :on_symbeg
372374
# can be indented if t.tok starts with `%`
373375
when :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_embexpr_beg
@@ -382,46 +384,70 @@ def calc_nesting_depth(opens)
382384
[indent_level, nesting_level]
383385
end
384386

385-
def free_indent_token(opens, line_index)
386-
last_token = opens.last
387-
return unless last_token
388-
if last_token.event == :on_heredoc_beg && last_token.pos.first < line_index + 1
389-
# accept extra indent spaces inside heredoc
390-
last_token
391-
end
392-
end
393-
394-
def process_indent_level(tokens, lines)
395-
opens = IRB::NestingParser.open_tokens(tokens)
396-
indent_level, _nesting_level = calc_nesting_depth(opens)
397-
indent = indent_level * 2
398-
line_index = lines.size - 2
399-
if free_indent_token(opens, line_index)
400-
return [indent, lines[line_index][/^ */].length].max
401-
end
387+
FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg]
402388

403-
indent
389+
def free_indent_token?(token)
390+
FREE_INDENT_TOKENS.include?(token&.event)
404391
end
405392

406-
def check_corresponding_token_depth(tokens, lines, line_index)
393+
def process_indent_level(tokens, lines, line_index, is_newline)
407394
line_results = IRB::NestingParser.parse_by_line(tokens)
408395
result = line_results[line_index]
409-
return unless result
396+
if result
397+
_tokens, prev_opens, next_opens, min_depth = result
398+
else
399+
# When last line is empty
400+
prev_opens = next_opens = line_results.last[2]
401+
min_depth = next_opens.size
402+
end
410403

411404
# To correctly indent line like `end.map do`, we use shortest open tokens on each line for indent calculation.
412405
# Shortest open tokens can be calculated by `opens.take(min_depth)`
413-
_tokens, prev_opens, opens, min_depth = result
414-
indent_level, _nesting_level = calc_nesting_depth(opens.take(min_depth))
415-
indent = indent_level * 2
416-
free_indent_tok = free_indent_token(opens, line_index)
417-
prev_line_free_indent_tok = free_indent_token(prev_opens, line_index - 1)
418-
if prev_line_free_indent_tok && prev_line_free_indent_tok != free_indent_tok
419-
return indent
420-
elsif free_indent_tok
421-
return lines[line_index][/^ */].length
406+
indent_level, _nesting_level = calc_nesting_depth(prev_opens.take(min_depth))
407+
indent = 2 * indent_level
408+
409+
preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size
410+
411+
prev_open_token = prev_opens.last
412+
next_open_token = next_opens.last
413+
414+
if free_indent_token?(prev_open_token)
415+
if is_newline && prev_open_token.pos[0] == line_index
416+
# First newline inside free-indent token
417+
indent
418+
else
419+
# Accept any number of indent inside free-indent token
420+
preserve_indent
421+
end
422+
elsif prev_open_token&.event == :on_embdoc_beg || next_open_token&.event == :on_embdoc_beg
423+
if prev_open_token&.event == next_open_token&.event
424+
# Accept any number of indent inside embdoc content
425+
preserve_indent
426+
else
427+
# =begin or =end
428+
0
429+
end
430+
elsif prev_open_token&.event == :on_heredoc_beg
431+
tok = prev_open_token.tok
432+
if prev_opens.size <= next_opens.size
433+
if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token
434+
# First line in heredoc
435+
indent
436+
elsif tok.match?(/^<<~/)
437+
# Accept extra indent spaces inside `<<~` heredoc
438+
[indent, preserve_indent].max
439+
else
440+
# Accept any number of indent inside other heredoc
441+
preserve_indent
442+
end
443+
else
444+
# Heredoc close
445+
prev_line_indent_level, _prev_line_nesting_level = calc_nesting_depth(prev_opens)
446+
tok.match?(/^<<[~-]/) ? 2 * (prev_line_indent_level - 1) : 0
447+
end
448+
else
449+
indent
422450
end
423-
prev_indent_level, _prev_nesting_level = calc_nesting_depth(prev_opens)
424-
indent if indent_level < prev_indent_level
425451
end
426452

427453
LTYPE_TOKENS = %i[

0 commit comments

Comments
 (0)