irb 1.14.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rdoc_options +5 -0
- data/CONTRIBUTING.md +52 -0
- data/EXTEND_IRB.md +3 -0
- data/Gemfile +11 -1
- data/README.md +13 -293
- data/doc/COMMAND_LINE_OPTIONS.md +69 -0
- data/doc/COMPARED_WITH_PRY.md +22 -0
- data/doc/Configurations.md +274 -0
- data/doc/EXTEND_IRB.md +122 -0
- data/doc/Index.md +705 -0
- data/doc/irb/irb.rd.ja +1 -1
- data/lib/irb/color.rb +251 -162
- data/lib/irb/color_printer.rb +10 -9
- data/lib/irb/command/base.rb +46 -13
- data/lib/irb/command/copy.rb +83 -0
- data/lib/irb/command/debug.rb +8 -6
- data/lib/irb/command/help.rb +1 -1
- data/lib/irb/command/history.rb +1 -1
- data/lib/irb/command/internal_helpers.rb +7 -4
- data/lib/irb/command/ls.rb +29 -15
- data/lib/irb/completion.rb +81 -57
- data/lib/irb/context.rb +132 -53
- data/lib/irb/debug/ui.rb +2 -4
- data/lib/irb/debug.rb +9 -12
- data/lib/irb/default_commands.rb +19 -8
- data/lib/irb/easter-egg.rb +13 -7
- data/lib/irb/ext/multi-irb.rb +2 -0
- data/lib/irb/history.rb +84 -55
- data/lib/irb/init.rb +7 -2
- data/lib/irb/input-method.rb +167 -130
- data/lib/irb/inspector.rb +42 -36
- data/lib/irb/lc/help-message +2 -2
- data/lib/irb/lc/ja/help-message +4 -4
- data/lib/irb/nesting_parser.rb +364 -213
- data/lib/irb/pager.rb +137 -7
- data/lib/irb/ruby-lex.rb +230 -302
- data/lib/irb/ruby_logo.aa +79 -37
- data/lib/irb/source_finder.rb +15 -22
- data/lib/irb/startup_message.rb +83 -0
- data/lib/irb/statement.rb +21 -0
- data/lib/irb/version.rb +2 -2
- data/lib/irb/workspace.rb +22 -31
- data/lib/irb.rb +148 -986
- data/man/irb.1 +37 -2
- metadata +42 -12
- data/.document +0 -4
- data/Rakefile +0 -54
- data/bin/console +0 -6
- data/bin/setup +0 -6
- data/irb.gemspec +0 -46
data/lib/irb/ruby-lex.rb
CHANGED
|
@@ -4,36 +4,39 @@
|
|
|
4
4
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
require "
|
|
7
|
+
require "prism"
|
|
8
8
|
require "jruby" if RUBY_ENGINE == "jruby"
|
|
9
9
|
require_relative "nesting_parser"
|
|
10
10
|
|
|
11
11
|
module IRB
|
|
12
12
|
# :stopdoc:
|
|
13
13
|
class RubyLex
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
14
|
+
LTYPE_TOKENS = %i[
|
|
15
|
+
on_heredoc_beg on_tstring_beg
|
|
16
|
+
on_regexp_beg on_symbeg on_backtick
|
|
17
|
+
on_symbols_beg on_qsymbols_beg
|
|
18
|
+
on_words_beg on_qwords_beg
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
RESERVED_WORDS = %i[
|
|
22
|
+
__ENCODING__ __LINE__ __FILE__
|
|
23
|
+
BEGIN END
|
|
24
|
+
alias and
|
|
25
|
+
begin break
|
|
26
|
+
case class
|
|
27
|
+
def defined? do
|
|
28
|
+
else elsif end ensure
|
|
29
|
+
false for
|
|
30
|
+
if in
|
|
31
|
+
module
|
|
32
|
+
next nil not
|
|
33
|
+
or
|
|
34
|
+
redo rescue retry return
|
|
35
|
+
self super
|
|
36
|
+
then true
|
|
37
|
+
undef unless until
|
|
38
|
+
when while
|
|
39
|
+
yield
|
|
37
40
|
]
|
|
38
41
|
|
|
39
42
|
class TerminateLineInput < StandardError
|
|
@@ -42,242 +45,173 @@ module IRB
|
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
def self.compile_with_errors_suppressed(code, line_no: 1)
|
|
46
|
-
begin
|
|
47
|
-
result = yield code, line_no
|
|
48
|
-
rescue ArgumentError
|
|
49
|
-
# Ruby can issue an error for the code if there is an
|
|
50
|
-
# incomplete magic comment for encoding in it. Force an
|
|
51
|
-
# expression with a new line before the code in this
|
|
52
|
-
# case to prevent magic comment handling. To make sure
|
|
53
|
-
# line numbers in the lexed code remain the same,
|
|
54
|
-
# decrease the line number by one.
|
|
55
|
-
code = ";\n#{code}"
|
|
56
|
-
line_no -= 1
|
|
57
|
-
result = yield code, line_no
|
|
58
|
-
end
|
|
59
|
-
result
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
ERROR_TOKENS = [
|
|
63
|
-
:on_parse_error,
|
|
64
|
-
:compile_error,
|
|
65
|
-
:on_assign_error,
|
|
66
|
-
:on_alias_error,
|
|
67
|
-
:on_class_name_error,
|
|
68
|
-
:on_param_error
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
def self.generate_local_variables_assign_code(local_variables)
|
|
72
|
-
"#{local_variables.join('=')}=nil;" unless local_variables.empty?
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Some part of the code is not included in Ripper's token.
|
|
76
|
-
# Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr.
|
|
77
|
-
# With interpolated tokens, tokens.map(&:tok).join will be equal to code.
|
|
78
|
-
def self.interpolate_ripper_ignored_tokens(code, tokens)
|
|
79
|
-
line_positions = [0]
|
|
80
|
-
code.lines.each do |line|
|
|
81
|
-
line_positions << line_positions.last + line.bytesize
|
|
82
|
-
end
|
|
83
|
-
prev_byte_pos = 0
|
|
84
|
-
interpolated = []
|
|
85
|
-
prev_line = 1
|
|
86
|
-
tokens.each do |t|
|
|
87
|
-
line, col = t.pos
|
|
88
|
-
byte_pos = line_positions[line - 1] + col
|
|
89
|
-
if prev_byte_pos < byte_pos
|
|
90
|
-
tok = code.byteslice(prev_byte_pos...byte_pos)
|
|
91
|
-
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
|
92
|
-
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
|
93
|
-
prev_line += tok.count("\n")
|
|
94
|
-
end
|
|
95
|
-
interpolated << t
|
|
96
|
-
prev_byte_pos = byte_pos + t.tok.bytesize
|
|
97
|
-
prev_line += t.tok.count("\n")
|
|
98
|
-
end
|
|
99
|
-
if prev_byte_pos < code.bytesize
|
|
100
|
-
tok = code.byteslice(prev_byte_pos..)
|
|
101
|
-
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
|
102
|
-
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
|
103
|
-
end
|
|
104
|
-
interpolated
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def self.ripper_lex_without_warning(code, local_variables: [])
|
|
108
|
-
verbose, $VERBOSE = $VERBOSE, nil
|
|
109
|
-
lvars_code = generate_local_variables_assign_code(local_variables)
|
|
110
|
-
original_code = code
|
|
111
|
-
if lvars_code
|
|
112
|
-
code = "#{lvars_code}\n#{code}"
|
|
113
|
-
line_no = 0
|
|
114
|
-
else
|
|
115
|
-
line_no = 1
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no|
|
|
119
|
-
lexer = Ripper::Lexer.new(inner_code, '-', line_no)
|
|
120
|
-
tokens = []
|
|
121
|
-
lexer.scan.each do |t|
|
|
122
|
-
next if t.pos.first == 0
|
|
123
|
-
prev_tk = tokens.last
|
|
124
|
-
position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize
|
|
125
|
-
if position_overlapped
|
|
126
|
-
tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event)
|
|
127
|
-
else
|
|
128
|
-
tokens << t
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
interpolate_ripper_ignored_tokens(original_code, tokens)
|
|
132
|
-
end
|
|
133
|
-
ensure
|
|
134
|
-
$VERBOSE = verbose
|
|
135
|
-
end
|
|
136
|
-
|
|
137
48
|
def check_code_state(code, local_variables:)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
49
|
+
parse_lex_result = Prism.parse_lex(code, scopes: [local_variables])
|
|
50
|
+
|
|
51
|
+
opens = NestingParser.open_nestings(parse_lex_result)
|
|
52
|
+
lines = code.lines
|
|
53
|
+
tokens = parse_lex_result.value[1].map(&:first).sort_by {|t| t.location.start_offset }
|
|
54
|
+
continue = should_continue?(tokens, lines.last, lines.size)
|
|
55
|
+
[continue, opens, code_terminated?(code, continue, opens, local_variables: local_variables)]
|
|
141
56
|
end
|
|
142
57
|
|
|
143
|
-
def code_terminated?(code,
|
|
58
|
+
def code_terminated?(code, continue, opens, local_variables:)
|
|
144
59
|
case check_code_syntax(code, local_variables: local_variables)
|
|
145
60
|
when :unrecoverable_error
|
|
146
61
|
true
|
|
147
62
|
when :recoverable_error
|
|
148
63
|
false
|
|
149
64
|
when :other_error
|
|
150
|
-
opens.empty? && !
|
|
65
|
+
opens.empty? && !continue
|
|
151
66
|
when :valid
|
|
152
|
-
!
|
|
67
|
+
!continue
|
|
153
68
|
end
|
|
154
69
|
end
|
|
155
70
|
|
|
156
71
|
def assignment_expression?(code, local_variables:)
|
|
157
|
-
#
|
|
158
|
-
# expressions is an assignment
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
72
|
+
# Parse the code and check if the last of possibly multiple
|
|
73
|
+
# expressions is an assignment node.
|
|
74
|
+
program_node = Prism.parse(code, scopes: [local_variables]).value
|
|
75
|
+
node = program_node.statements.body.last
|
|
76
|
+
case node
|
|
77
|
+
when nil
|
|
78
|
+
# Empty code, comment-only code or invalid code
|
|
79
|
+
false
|
|
80
|
+
when Prism::CallNode
|
|
81
|
+
# a.b = 1, a[b] = 1
|
|
82
|
+
# Prism::CallNode#equal_loc is only available in prism >= 1.7.0
|
|
83
|
+
if node.name == :[]=
|
|
84
|
+
# Distinguish between `a[k] = v` from `a.[]= k, v`, `a.[]=(k, v)`
|
|
85
|
+
node.opening == '['
|
|
86
|
+
else
|
|
87
|
+
node.name.end_with?('=')
|
|
88
|
+
end
|
|
89
|
+
when Prism::MatchWriteNode
|
|
90
|
+
# /(?<lvar>)/ =~ a, Class name is *WriteNode but not an assignment.
|
|
91
|
+
false
|
|
92
|
+
else
|
|
93
|
+
# a = 1, @a = 1, $a = 1, @@a = 1, A = 1, a += 1, a &&= 1, a.b += 1, and so on
|
|
94
|
+
node.class.name.match?(/WriteNode/)
|
|
95
|
+
end
|
|
172
96
|
end
|
|
173
97
|
|
|
174
|
-
def should_continue?(tokens)
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
98
|
+
def should_continue?(tokens, line, line_num)
|
|
99
|
+
# Check if the line ends with \\. Then IRB should continue reading next line.
|
|
100
|
+
# Space and backslash are not included in Prism token, so find trailing text after last non-newline token position.
|
|
101
|
+
trailing = line
|
|
102
|
+
tokens.reverse_each do |t|
|
|
103
|
+
break if t.location.start_line < line_num
|
|
104
|
+
if t.location.start_line == line_num &&
|
|
105
|
+
t.location.end_line == line_num &&
|
|
106
|
+
t.type != :IGNORED_NEWLINE &&
|
|
107
|
+
t.type != :NEWLINE &&
|
|
108
|
+
t.type != :EOF
|
|
109
|
+
trailing = line.byteslice(t.location.end_column..)
|
|
110
|
+
trailing ||= '' # in case end_line is wrong (e.g. `"\C-`)
|
|
111
|
+
break
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
return true if trailing.match?(/\A\s*\\\n?\z/)
|
|
115
|
+
|
|
116
|
+
# "1 + \n" and "foo.\n" should continue.
|
|
117
|
+
pos = tokens.size - 1
|
|
118
|
+
ignored_newline_found = false
|
|
119
|
+
while pos >= 0
|
|
120
|
+
case tokens[pos].type
|
|
121
|
+
when :EMBDOC_BEGIN, :EMBDOC_LINE, :EMBDOC_END, :COMMENT, :EOF
|
|
122
|
+
pos -= 1
|
|
123
|
+
when :IGNORED_NEWLINE
|
|
124
|
+
pos -= 1
|
|
125
|
+
ignored_newline_found = true
|
|
187
126
|
else
|
|
188
|
-
|
|
189
|
-
return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/)
|
|
190
|
-
|
|
191
|
-
# EXPR_DOT and most of the EXPR_BEG should continue
|
|
192
|
-
return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT)
|
|
127
|
+
break
|
|
193
128
|
end
|
|
194
129
|
end
|
|
195
|
-
|
|
130
|
+
|
|
131
|
+
# If IGNORED_NEWLINE token is following non-newline non-semicolon token, it should continue.
|
|
132
|
+
# Special case: treat `1..` and `1...` as not continuing.
|
|
133
|
+
ignored_newline_found && pos >= 0 && !%i[DOT_DOT DOT_DOT_DOT NEWLINE SEMICOLON].include?(tokens[pos].type)
|
|
196
134
|
end
|
|
197
135
|
|
|
198
136
|
def check_code_syntax(code, local_variables:)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
137
|
+
result = Prism.lex(code, scopes: [local_variables])
|
|
138
|
+
if result.success?
|
|
139
|
+
:valid
|
|
140
|
+
elsif result.respond_to?(:continuable?)
|
|
141
|
+
result.continuable? ? :recoverable_error : :unrecoverable_error
|
|
142
|
+
else # For Prism <= 1.9.0. Drop this branch when IRB requires Prism >= 1.10.0.
|
|
143
|
+
check_syntax_error_heuristics(result)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Prism <= 1.9.0 does not have `ParseResult#continuable?` method.
|
|
148
|
+
# Fallback to legacy heuristics based on error messages and error locations.
|
|
149
|
+
def check_syntax_error_heuristics(prism_parse_result)
|
|
150
|
+
|
|
151
|
+
# Get the token excluding trailing comments and newlines
|
|
152
|
+
# to compare error location with the last or second-last meaningful token location
|
|
153
|
+
tokens = prism_parse_result.value.map(&:first)
|
|
154
|
+
until tokens.empty?
|
|
155
|
+
case tokens.last.type
|
|
156
|
+
when :COMMENT, :NEWLINE, :IGNORED_NEWLINE, :EMBDOC_BEGIN, :EMBDOC_LINE, :EMBDOC_END, :EOF
|
|
157
|
+
tokens.pop
|
|
211
158
|
else
|
|
212
|
-
|
|
213
|
-
eval("BEGIN { throw :valid, true }\n#{code}")
|
|
214
|
-
false
|
|
215
|
-
end
|
|
159
|
+
break
|
|
216
160
|
end
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
case
|
|
222
|
-
when /unexpected
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
unknown = false
|
|
164
|
+
prism_parse_result.errors.each do |error|
|
|
165
|
+
case error.message
|
|
166
|
+
when /unexpected character literal|incomplete expression at|unexpected .%.|too short escape sequence/i
|
|
167
|
+
# Ignore these errors. Likely to appear only at the end of code.
|
|
168
|
+
# `[a, b ?` unexpected character literal, incomplete expression at
|
|
169
|
+
# `p a, %` unexpected '%'
|
|
170
|
+
# `/\u` too short escape sequence
|
|
171
|
+
when /unexpected write target/i
|
|
172
|
+
# `a,b` recoverable by `=v`
|
|
173
|
+
# `a,b,` recoverable by `c=v`
|
|
174
|
+
tok = tokens.last
|
|
175
|
+
tok = tokens[-2] if tok&.type == :COMMA
|
|
176
|
+
return :unrecoverable_error if tok && error.location.end_offset < tok.location.end_offset
|
|
177
|
+
when /(invalid|unexpected) (?:break|next|redo)/i
|
|
178
|
+
# Hard to check correctly, so treat it as always recoverable.
|
|
179
|
+
# `(break;1)` recoverable by `.f while true`
|
|
180
|
+
when / meets end of file|unexpected end-of-input|unterminated |cannot parse|could not parse/i
|
|
181
|
+
# These are recoverable errors if there is no other unrecoverable error
|
|
182
|
+
# `/aaa` unterminated regexp meets end of file
|
|
183
|
+
# `def f` unexpected end-of-input
|
|
184
|
+
# `"#{` unterminated string
|
|
185
|
+
# `:"aa` cannot parse the string part
|
|
186
|
+
# `def f =` could not parse the endless method body
|
|
187
|
+
when /is not allowed|unexpected .+ ignoring it/i
|
|
188
|
+
# `@@` `$--` is not allowed
|
|
189
|
+
# `)`, `end` unexpected ')', ignoring it
|
|
243
190
|
return :unrecoverable_error
|
|
244
|
-
when /
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
return :recoverable_error
|
|
255
|
-
when /unexpected end-of-input/
|
|
256
|
-
# "syntax error, unexpected end-of-input, expecting keyword_end"
|
|
257
|
-
#
|
|
258
|
-
# example:
|
|
259
|
-
# if true
|
|
260
|
-
# hoge
|
|
261
|
-
# if false
|
|
262
|
-
# fuga
|
|
263
|
-
# end
|
|
264
|
-
return :recoverable_error
|
|
191
|
+
when /unexpected |invalid |dynamic constant assignment|can't set variable|can't change the value|is not valid to get|variable capture in alternative pattern/i
|
|
192
|
+
# Likely to be unrecoverable except when the error is at the last token location.
|
|
193
|
+
# Unexpected: `class a`, `tap(&`, `def f(a,`
|
|
194
|
+
# Invalid: `a ? b :`, `/\u{`, `"\M-`
|
|
195
|
+
# `a,B` recoverable by `.c=v` dynamic constant assignment
|
|
196
|
+
# `a,$1` recoverable by `.f=v` Can't set variable
|
|
197
|
+
# `a,self` recoverable by `.f=v` Can't change the value of self
|
|
198
|
+
# `p foo?:` recoverable by `v` is not valid to get
|
|
199
|
+
# `x in 1|{x:` recoverable by `1}` variable capture in alternative pattern
|
|
200
|
+
return :unrecoverable_error if tokens.last && error.location.end_offset <= tokens.last.location.start_offset
|
|
265
201
|
else
|
|
266
|
-
|
|
202
|
+
unknown = true
|
|
267
203
|
end
|
|
268
|
-
ensure
|
|
269
|
-
$VERBOSE = verbose
|
|
270
204
|
end
|
|
271
|
-
:
|
|
205
|
+
unknown ? :other_error : :recoverable_error
|
|
272
206
|
end
|
|
273
207
|
|
|
274
208
|
def calc_indent_level(opens)
|
|
275
209
|
indent_level = 0
|
|
276
|
-
opens.each_with_index do |
|
|
277
|
-
case
|
|
210
|
+
opens.each_with_index do |elem, index|
|
|
211
|
+
case elem.event
|
|
278
212
|
when :on_heredoc_beg
|
|
279
213
|
if opens[index + 1]&.event != :on_heredoc_beg
|
|
280
|
-
if
|
|
214
|
+
if elem.tok.match?(/^<<[~-]/)
|
|
281
215
|
indent_level += 1
|
|
282
216
|
else
|
|
283
217
|
indent_level = 0
|
|
@@ -286,50 +220,50 @@ module IRB
|
|
|
286
220
|
when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick
|
|
287
221
|
# No indent: "", //, :"", ``
|
|
288
222
|
# Indent: %(), %r(), %i(), %x()
|
|
289
|
-
indent_level += 1 if
|
|
223
|
+
indent_level += 1 if elem.tok.start_with? '%'
|
|
290
224
|
when :on_embdoc_beg
|
|
291
225
|
indent_level = 0
|
|
292
226
|
else
|
|
293
|
-
indent_level += 1 unless
|
|
227
|
+
indent_level += 1 unless elem.tok == 'alias' || elem.tok == 'undef'
|
|
294
228
|
end
|
|
295
229
|
end
|
|
296
230
|
indent_level
|
|
297
231
|
end
|
|
298
232
|
|
|
299
|
-
|
|
233
|
+
FREE_INDENT_NESTINGS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg]
|
|
300
234
|
|
|
301
|
-
def
|
|
302
|
-
|
|
235
|
+
def free_indent_nesting_element?(elem)
|
|
236
|
+
FREE_INDENT_NESTINGS.include?(elem&.event)
|
|
303
237
|
end
|
|
304
238
|
|
|
305
239
|
# Calculates the difference of pasted code's indent and indent calculated from tokens
|
|
306
240
|
def indent_difference(lines, line_results, line_index)
|
|
307
241
|
loop do
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if !
|
|
242
|
+
prev_opens, _next_opens, min_depth = line_results[line_index]
|
|
243
|
+
open_elem = prev_opens.last
|
|
244
|
+
if !open_elem || (open_elem.event != :on_heredoc_beg && !free_indent_nesting_element?(open_elem))
|
|
311
245
|
# If the leading whitespace is an indent, return the difference
|
|
312
246
|
indent_level = calc_indent_level(prev_opens.take(min_depth))
|
|
313
247
|
calculated_indent = 2 * indent_level
|
|
314
248
|
actual_indent = lines[line_index][/^ */].size
|
|
315
249
|
return actual_indent - calculated_indent
|
|
316
|
-
elsif
|
|
250
|
+
elsif open_elem.event == :on_heredoc_beg && open_elem.tok.match?(/^<<[^-~]/)
|
|
317
251
|
return 0
|
|
318
252
|
end
|
|
319
253
|
# If the leading whitespace is not an indent but part of a multiline token
|
|
320
254
|
# Calculate base_indent of the multiline token's beginning line
|
|
321
|
-
line_index =
|
|
255
|
+
line_index = open_elem.pos[0] - 1
|
|
322
256
|
end
|
|
323
257
|
end
|
|
324
258
|
|
|
325
|
-
def process_indent_level(
|
|
326
|
-
line_results = NestingParser.parse_by_line(
|
|
259
|
+
def process_indent_level(parse_lex_result, lines, line_index, is_newline)
|
|
260
|
+
line_results = NestingParser.parse_by_line(parse_lex_result)
|
|
327
261
|
result = line_results[line_index]
|
|
328
262
|
if result
|
|
329
|
-
|
|
263
|
+
prev_opens, next_opens, min_depth = result
|
|
330
264
|
else
|
|
331
265
|
# When last line is empty
|
|
332
|
-
prev_opens = next_opens = line_results.last[
|
|
266
|
+
prev_opens = next_opens = line_results.last[1]
|
|
333
267
|
min_depth = next_opens.size
|
|
334
268
|
end
|
|
335
269
|
|
|
@@ -339,39 +273,39 @@ module IRB
|
|
|
339
273
|
|
|
340
274
|
preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size
|
|
341
275
|
|
|
342
|
-
|
|
343
|
-
|
|
276
|
+
prev_open_elem = prev_opens.last
|
|
277
|
+
next_open_elem = next_opens.last
|
|
344
278
|
|
|
345
|
-
# Calculates base indent for pasted code on the line where
|
|
346
|
-
# irb(main):001:1* if a # base_indent is 2, indent calculated from
|
|
347
|
-
# irb(main):002:1* if b # base_indent is 6, indent calculated from
|
|
348
|
-
# irb(main):003:0> c # base_indent is 6, indent calculated from
|
|
349
|
-
if
|
|
350
|
-
base_indent = [0, indent_difference(lines, line_results,
|
|
279
|
+
# Calculates base indent for pasted code on the line where prev_open_elem is located
|
|
280
|
+
# irb(main):001:1* if a # base_indent is 2, indent calculated from nestings is 0
|
|
281
|
+
# irb(main):002:1* if b # base_indent is 6, indent calculated from nestings is 2
|
|
282
|
+
# irb(main):003:0> c # base_indent is 6, indent calculated from nestings is 4
|
|
283
|
+
if prev_open_elem
|
|
284
|
+
base_indent = [0, indent_difference(lines, line_results, prev_open_elem.pos[0] - 1)].max
|
|
351
285
|
else
|
|
352
286
|
base_indent = 0
|
|
353
287
|
end
|
|
354
288
|
|
|
355
|
-
if
|
|
356
|
-
if is_newline &&
|
|
289
|
+
if free_indent_nesting_element?(prev_open_elem)
|
|
290
|
+
if is_newline && prev_open_elem.pos[0] == line_index
|
|
357
291
|
# First newline inside free-indent token
|
|
358
292
|
base_indent + indent
|
|
359
293
|
else
|
|
360
294
|
# Accept any number of indent inside free-indent token
|
|
361
295
|
preserve_indent
|
|
362
296
|
end
|
|
363
|
-
elsif
|
|
364
|
-
if
|
|
297
|
+
elsif prev_open_elem&.event == :on_embdoc_beg || next_open_elem&.event == :on_embdoc_beg
|
|
298
|
+
if prev_open_elem&.event == next_open_elem&.event
|
|
365
299
|
# Accept any number of indent inside embdoc content
|
|
366
300
|
preserve_indent
|
|
367
301
|
else
|
|
368
302
|
# =begin or =end
|
|
369
303
|
0
|
|
370
304
|
end
|
|
371
|
-
elsif
|
|
372
|
-
tok =
|
|
305
|
+
elsif prev_open_elem&.event == :on_heredoc_beg
|
|
306
|
+
tok = prev_open_elem.tok
|
|
373
307
|
if prev_opens.size <= next_opens.size
|
|
374
|
-
if is_newline && lines[line_index].empty? && line_results[line_index - 1][
|
|
308
|
+
if is_newline && lines[line_index].empty? && line_results[line_index - 1][0].last != next_open_elem
|
|
375
309
|
# First line in heredoc
|
|
376
310
|
tok.match?(/^<<[-~]/) ? base_indent + indent : indent
|
|
377
311
|
elsif tok.match?(/^<<~/)
|
|
@@ -391,22 +325,15 @@ module IRB
|
|
|
391
325
|
end
|
|
392
326
|
end
|
|
393
327
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
on_symbols_beg on_qsymbols_beg
|
|
398
|
-
on_words_beg on_qwords_beg
|
|
399
|
-
]
|
|
400
|
-
|
|
401
|
-
def ltype_from_open_tokens(opens)
|
|
402
|
-
start_token = opens.reverse_each.find do |tok|
|
|
403
|
-
LTYPE_TOKENS.include?(tok.event)
|
|
328
|
+
def ltype_from_open_nestings(opens)
|
|
329
|
+
start_nesting = opens.reverse_each.find do |elem|
|
|
330
|
+
LTYPE_TOKENS.include?(elem.event)
|
|
404
331
|
end
|
|
405
|
-
return nil unless
|
|
332
|
+
return nil unless start_nesting
|
|
406
333
|
|
|
407
|
-
case
|
|
334
|
+
case start_nesting&.event
|
|
408
335
|
when :on_tstring_beg
|
|
409
|
-
case
|
|
336
|
+
case start_nesting&.tok
|
|
410
337
|
when ?" then ?"
|
|
411
338
|
when /^%.$/ then ?"
|
|
412
339
|
when /^%Q.$/ then ?"
|
|
@@ -421,50 +348,51 @@ module IRB
|
|
|
421
348
|
when :on_qsymbols_beg then ?]
|
|
422
349
|
when :on_symbols_beg then ?]
|
|
423
350
|
when :on_heredoc_beg
|
|
424
|
-
|
|
351
|
+
start_nesting&.tok =~ /<<[-~]?(['"`])\w+\1/
|
|
425
352
|
$1 || ?"
|
|
426
353
|
else
|
|
427
354
|
nil
|
|
428
355
|
end
|
|
429
356
|
end
|
|
430
357
|
|
|
358
|
+
# Check if <tt>code.lines[...-1]</tt> is terminated and can be evaluated immediately.
|
|
359
|
+
# Returns the last line string if terminated, otherwise false.
|
|
360
|
+
# Terminated means previous lines(<tt>code.lines[...-1]</tt>) is syntax valid and
|
|
361
|
+
# previous lines and the last line are syntactically separated.
|
|
362
|
+
# Terminated example
|
|
363
|
+
# foo(
|
|
364
|
+
# bar)
|
|
365
|
+
# baz.
|
|
366
|
+
# Unterminated example: previous lines are syntax invalid
|
|
367
|
+
# foo(
|
|
368
|
+
# bar).
|
|
369
|
+
# baz
|
|
370
|
+
# Unterminated example: previous lines are connected to the last line
|
|
371
|
+
# foo(
|
|
372
|
+
# bar)
|
|
373
|
+
# .baz
|
|
431
374
|
def check_termination_in_prev_line(code, local_variables:)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
index = tokens.rindex do |t|
|
|
435
|
-
# traverse first token before last line
|
|
436
|
-
if past_first_newline
|
|
437
|
-
if t.tok.include?("\n")
|
|
438
|
-
true
|
|
439
|
-
end
|
|
440
|
-
elsif t.tok.include?("\n")
|
|
441
|
-
past_first_newline = true
|
|
442
|
-
false
|
|
443
|
-
else
|
|
444
|
-
false
|
|
445
|
-
end
|
|
446
|
-
end
|
|
375
|
+
lines = code.lines
|
|
376
|
+
return false if lines.size < 2
|
|
447
377
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
|
|
451
|
-
last_line_tokens.each do |t|
|
|
452
|
-
unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event)
|
|
453
|
-
first_token = t
|
|
454
|
-
break
|
|
455
|
-
end
|
|
456
|
-
end
|
|
378
|
+
prev_line_result = Prism.parse(lines[...-1].join, scopes: [local_variables])
|
|
379
|
+
return false unless prev_line_result.success?
|
|
457
380
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
end
|
|
465
|
-
end
|
|
381
|
+
prev_nodes = prev_line_result.value.statements.body
|
|
382
|
+
whole_nodes = Prism.parse(code, scopes: [local_variables]).value.statements.body
|
|
383
|
+
|
|
384
|
+
return false if whole_nodes.size < prev_nodes.size
|
|
385
|
+
return false unless prev_nodes.zip(whole_nodes).all? do |a, b|
|
|
386
|
+
a.location == b.location
|
|
466
387
|
end
|
|
467
|
-
|
|
388
|
+
|
|
389
|
+
# If the last line only contain comments, treat it as not connected to handle this case:
|
|
390
|
+
# receiver
|
|
391
|
+
# # comment
|
|
392
|
+
# .method
|
|
393
|
+
return false if lines.last.match?(/\A\s*#/)
|
|
394
|
+
|
|
395
|
+
lines.last
|
|
468
396
|
end
|
|
469
397
|
end
|
|
470
398
|
# :startdoc:
|