irb 1.16.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/Gemfile +8 -2
- data/lib/irb/color.rb +251 -163
- data/lib/irb/command/base.rb +35 -0
- data/lib/irb/command/copy.rb +11 -1
- data/lib/irb/command/internal_helpers.rb +6 -3
- data/lib/irb/command/ls.rb +6 -4
- data/lib/irb/completion.rb +38 -16
- data/lib/irb/context.rb +1 -1
- data/lib/irb/debug.rb +2 -2
- data/lib/irb/init.rb +3 -0
- data/lib/irb/input-method.rb +141 -111
- data/lib/irb/nesting_parser.rb +362 -213
- data/lib/irb/pager.rb +8 -0
- data/lib/irb/ruby-lex.rb +204 -303
- data/lib/irb/ruby_logo.aa +4 -0
- data/lib/irb/source_finder.rb +5 -14
- data/lib/irb/startup_message.rb +83 -0
- data/lib/irb/version.rb +2 -2
- data/lib/irb.rb +39 -17
- metadata +16 -1
data/lib/irb/ruby-lex.rb
CHANGED
|
@@ -4,47 +4,13 @@
|
|
|
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
|
-
ASSIGNMENT_NODE_TYPES = [
|
|
15
|
-
# Local, instance, global, class, constant, instance, and index assignment:
|
|
16
|
-
# "foo = bar",
|
|
17
|
-
# "@foo = bar",
|
|
18
|
-
# "$foo = bar",
|
|
19
|
-
# "@@foo = bar",
|
|
20
|
-
# "::Foo = bar",
|
|
21
|
-
# "a::Foo = bar",
|
|
22
|
-
# "Foo = bar"
|
|
23
|
-
# "foo.bar = 1"
|
|
24
|
-
# "foo[1] = bar"
|
|
25
|
-
:assign,
|
|
26
|
-
|
|
27
|
-
# Operation assignment:
|
|
28
|
-
# "foo += bar"
|
|
29
|
-
# "foo -= bar"
|
|
30
|
-
# "foo ||= bar"
|
|
31
|
-
# "foo &&= bar"
|
|
32
|
-
:opassign,
|
|
33
|
-
|
|
34
|
-
# Multiple assignment:
|
|
35
|
-
# "foo, bar = 1, 2
|
|
36
|
-
:massign,
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
ERROR_TOKENS = [
|
|
40
|
-
:on_parse_error,
|
|
41
|
-
:compile_error,
|
|
42
|
-
:on_assign_error,
|
|
43
|
-
:on_alias_error,
|
|
44
|
-
:on_class_name_error,
|
|
45
|
-
:on_param_error
|
|
46
|
-
]
|
|
47
|
-
|
|
48
14
|
LTYPE_TOKENS = %i[
|
|
49
15
|
on_heredoc_beg on_tstring_beg
|
|
50
16
|
on_regexp_beg on_symbeg on_backtick
|
|
@@ -79,239 +45,173 @@ module IRB
|
|
|
79
45
|
end
|
|
80
46
|
end
|
|
81
47
|
|
|
82
|
-
class << self
|
|
83
|
-
def compile_with_errors_suppressed(code, line_no: 1)
|
|
84
|
-
begin
|
|
85
|
-
result = yield code, line_no
|
|
86
|
-
rescue ArgumentError
|
|
87
|
-
# Ruby can issue an error for the code if there is an
|
|
88
|
-
# incomplete magic comment for encoding in it. Force an
|
|
89
|
-
# expression with a new line before the code in this
|
|
90
|
-
# case to prevent magic comment handling. To make sure
|
|
91
|
-
# line numbers in the lexed code remain the same,
|
|
92
|
-
# decrease the line number by one.
|
|
93
|
-
code = ";\n#{code}"
|
|
94
|
-
line_no -= 1
|
|
95
|
-
result = yield code, line_no
|
|
96
|
-
end
|
|
97
|
-
result
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def generate_local_variables_assign_code(local_variables)
|
|
101
|
-
# Some reserved words could be a local variable
|
|
102
|
-
# Example: def f(if: 1); binding.irb; end
|
|
103
|
-
# These reserved words should be removed from assignment code
|
|
104
|
-
local_variables -= RESERVED_WORDS
|
|
105
|
-
"#{local_variables.join('=')}=nil;" unless local_variables.empty?
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Some part of the code is not included in Ripper's token.
|
|
109
|
-
# Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr.
|
|
110
|
-
# With interpolated tokens, tokens.map(&:tok).join will be equal to code.
|
|
111
|
-
def interpolate_ripper_ignored_tokens(code, tokens)
|
|
112
|
-
line_positions = [0]
|
|
113
|
-
code.lines.each do |line|
|
|
114
|
-
line_positions << line_positions.last + line.bytesize
|
|
115
|
-
end
|
|
116
|
-
prev_byte_pos = 0
|
|
117
|
-
interpolated = []
|
|
118
|
-
prev_line = 1
|
|
119
|
-
tokens.each do |t|
|
|
120
|
-
line, col = t.pos
|
|
121
|
-
byte_pos = line_positions[line - 1] + col
|
|
122
|
-
if prev_byte_pos < byte_pos
|
|
123
|
-
tok = code.byteslice(prev_byte_pos...byte_pos)
|
|
124
|
-
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
|
125
|
-
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
|
126
|
-
prev_line += tok.count("\n")
|
|
127
|
-
end
|
|
128
|
-
interpolated << t
|
|
129
|
-
prev_byte_pos = byte_pos + t.tok.bytesize
|
|
130
|
-
prev_line += t.tok.count("\n")
|
|
131
|
-
end
|
|
132
|
-
if prev_byte_pos < code.bytesize
|
|
133
|
-
tok = code.byteslice(prev_byte_pos..)
|
|
134
|
-
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
|
135
|
-
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
|
136
|
-
end
|
|
137
|
-
interpolated
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def ripper_lex_without_warning(code, local_variables: [])
|
|
141
|
-
verbose, $VERBOSE = $VERBOSE, nil
|
|
142
|
-
lvars_code = generate_local_variables_assign_code(local_variables)
|
|
143
|
-
original_code = code
|
|
144
|
-
if lvars_code
|
|
145
|
-
code = "#{lvars_code}\n#{code}"
|
|
146
|
-
line_no = 0
|
|
147
|
-
else
|
|
148
|
-
line_no = 1
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no|
|
|
152
|
-
lexer = Ripper::Lexer.new(inner_code, '-', line_no)
|
|
153
|
-
tokens = []
|
|
154
|
-
lexer.scan.each do |t|
|
|
155
|
-
next if t.pos.first == 0
|
|
156
|
-
prev_tk = tokens.last
|
|
157
|
-
position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize
|
|
158
|
-
if position_overlapped
|
|
159
|
-
tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event)
|
|
160
|
-
else
|
|
161
|
-
tokens << t
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
interpolate_ripper_ignored_tokens(original_code, tokens)
|
|
165
|
-
end
|
|
166
|
-
ensure
|
|
167
|
-
$VERBOSE = verbose
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
48
|
def check_code_state(code, local_variables:)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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)]
|
|
175
56
|
end
|
|
176
57
|
|
|
177
|
-
def code_terminated?(code,
|
|
58
|
+
def code_terminated?(code, continue, opens, local_variables:)
|
|
178
59
|
case check_code_syntax(code, local_variables: local_variables)
|
|
179
60
|
when :unrecoverable_error
|
|
180
61
|
true
|
|
181
62
|
when :recoverable_error
|
|
182
63
|
false
|
|
183
64
|
when :other_error
|
|
184
|
-
opens.empty? && !
|
|
65
|
+
opens.empty? && !continue
|
|
185
66
|
when :valid
|
|
186
|
-
!
|
|
67
|
+
!continue
|
|
187
68
|
end
|
|
188
69
|
end
|
|
189
70
|
|
|
190
71
|
def assignment_expression?(code, local_variables:)
|
|
191
|
-
#
|
|
192
|
-
# expressions is an assignment
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
206
96
|
end
|
|
207
97
|
|
|
208
|
-
def should_continue?(tokens)
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
221
126
|
else
|
|
222
|
-
|
|
223
|
-
return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/)
|
|
224
|
-
|
|
225
|
-
# EXPR_DOT and most of the EXPR_BEG should continue
|
|
226
|
-
return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT)
|
|
127
|
+
break
|
|
227
128
|
end
|
|
228
129
|
end
|
|
229
|
-
|
|
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)
|
|
230
134
|
end
|
|
231
135
|
|
|
232
136
|
def check_code_syntax(code, local_variables:)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
|
245
158
|
else
|
|
246
|
-
|
|
247
|
-
eval("BEGIN { throw :valid, true }\n#{code}")
|
|
248
|
-
false
|
|
249
|
-
end
|
|
159
|
+
break
|
|
250
160
|
end
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
case
|
|
256
|
-
when /unexpected
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
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
|
|
277
190
|
return :unrecoverable_error
|
|
278
|
-
when /
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
|
|
288
|
-
return :recoverable_error
|
|
289
|
-
when /unexpected end-of-input/
|
|
290
|
-
# "syntax error, unexpected end-of-input, expecting keyword_end"
|
|
291
|
-
#
|
|
292
|
-
# example:
|
|
293
|
-
# if true
|
|
294
|
-
# hoge
|
|
295
|
-
# if false
|
|
296
|
-
# fuga
|
|
297
|
-
# end
|
|
298
|
-
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
|
|
299
201
|
else
|
|
300
|
-
|
|
202
|
+
unknown = true
|
|
301
203
|
end
|
|
302
|
-
ensure
|
|
303
|
-
$VERBOSE = verbose
|
|
304
204
|
end
|
|
305
|
-
:
|
|
205
|
+
unknown ? :other_error : :recoverable_error
|
|
306
206
|
end
|
|
307
207
|
|
|
308
208
|
def calc_indent_level(opens)
|
|
309
209
|
indent_level = 0
|
|
310
|
-
opens.each_with_index do |
|
|
311
|
-
case
|
|
210
|
+
opens.each_with_index do |elem, index|
|
|
211
|
+
case elem.event
|
|
312
212
|
when :on_heredoc_beg
|
|
313
213
|
if opens[index + 1]&.event != :on_heredoc_beg
|
|
314
|
-
if
|
|
214
|
+
if elem.tok.match?(/^<<[~-]/)
|
|
315
215
|
indent_level += 1
|
|
316
216
|
else
|
|
317
217
|
indent_level = 0
|
|
@@ -320,50 +220,50 @@ module IRB
|
|
|
320
220
|
when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick
|
|
321
221
|
# No indent: "", //, :"", ``
|
|
322
222
|
# Indent: %(), %r(), %i(), %x()
|
|
323
|
-
indent_level += 1 if
|
|
223
|
+
indent_level += 1 if elem.tok.start_with? '%'
|
|
324
224
|
when :on_embdoc_beg
|
|
325
225
|
indent_level = 0
|
|
326
226
|
else
|
|
327
|
-
indent_level += 1 unless
|
|
227
|
+
indent_level += 1 unless elem.tok == 'alias' || elem.tok == 'undef'
|
|
328
228
|
end
|
|
329
229
|
end
|
|
330
230
|
indent_level
|
|
331
231
|
end
|
|
332
232
|
|
|
333
|
-
|
|
233
|
+
FREE_INDENT_NESTINGS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg]
|
|
334
234
|
|
|
335
|
-
def
|
|
336
|
-
|
|
235
|
+
def free_indent_nesting_element?(elem)
|
|
236
|
+
FREE_INDENT_NESTINGS.include?(elem&.event)
|
|
337
237
|
end
|
|
338
238
|
|
|
339
239
|
# Calculates the difference of pasted code's indent and indent calculated from tokens
|
|
340
240
|
def indent_difference(lines, line_results, line_index)
|
|
341
241
|
loop do
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
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))
|
|
345
245
|
# If the leading whitespace is an indent, return the difference
|
|
346
246
|
indent_level = calc_indent_level(prev_opens.take(min_depth))
|
|
347
247
|
calculated_indent = 2 * indent_level
|
|
348
248
|
actual_indent = lines[line_index][/^ */].size
|
|
349
249
|
return actual_indent - calculated_indent
|
|
350
|
-
elsif
|
|
250
|
+
elsif open_elem.event == :on_heredoc_beg && open_elem.tok.match?(/^<<[^-~]/)
|
|
351
251
|
return 0
|
|
352
252
|
end
|
|
353
253
|
# If the leading whitespace is not an indent but part of a multiline token
|
|
354
254
|
# Calculate base_indent of the multiline token's beginning line
|
|
355
|
-
line_index =
|
|
255
|
+
line_index = open_elem.pos[0] - 1
|
|
356
256
|
end
|
|
357
257
|
end
|
|
358
258
|
|
|
359
|
-
def process_indent_level(
|
|
360
|
-
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)
|
|
361
261
|
result = line_results[line_index]
|
|
362
262
|
if result
|
|
363
|
-
|
|
263
|
+
prev_opens, next_opens, min_depth = result
|
|
364
264
|
else
|
|
365
265
|
# When last line is empty
|
|
366
|
-
prev_opens = next_opens = line_results.last[
|
|
266
|
+
prev_opens = next_opens = line_results.last[1]
|
|
367
267
|
min_depth = next_opens.size
|
|
368
268
|
end
|
|
369
269
|
|
|
@@ -373,39 +273,39 @@ module IRB
|
|
|
373
273
|
|
|
374
274
|
preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size
|
|
375
275
|
|
|
376
|
-
|
|
377
|
-
|
|
276
|
+
prev_open_elem = prev_opens.last
|
|
277
|
+
next_open_elem = next_opens.last
|
|
378
278
|
|
|
379
|
-
# Calculates base indent for pasted code on the line where
|
|
380
|
-
# irb(main):001:1* if a # base_indent is 2, indent calculated from
|
|
381
|
-
# irb(main):002:1* if b # base_indent is 6, indent calculated from
|
|
382
|
-
# irb(main):003:0> c # base_indent is 6, indent calculated from
|
|
383
|
-
if
|
|
384
|
-
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
|
|
385
285
|
else
|
|
386
286
|
base_indent = 0
|
|
387
287
|
end
|
|
388
288
|
|
|
389
|
-
if
|
|
390
|
-
if is_newline &&
|
|
289
|
+
if free_indent_nesting_element?(prev_open_elem)
|
|
290
|
+
if is_newline && prev_open_elem.pos[0] == line_index
|
|
391
291
|
# First newline inside free-indent token
|
|
392
292
|
base_indent + indent
|
|
393
293
|
else
|
|
394
294
|
# Accept any number of indent inside free-indent token
|
|
395
295
|
preserve_indent
|
|
396
296
|
end
|
|
397
|
-
elsif
|
|
398
|
-
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
|
|
399
299
|
# Accept any number of indent inside embdoc content
|
|
400
300
|
preserve_indent
|
|
401
301
|
else
|
|
402
302
|
# =begin or =end
|
|
403
303
|
0
|
|
404
304
|
end
|
|
405
|
-
elsif
|
|
406
|
-
tok =
|
|
305
|
+
elsif prev_open_elem&.event == :on_heredoc_beg
|
|
306
|
+
tok = prev_open_elem.tok
|
|
407
307
|
if prev_opens.size <= next_opens.size
|
|
408
|
-
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
|
|
409
309
|
# First line in heredoc
|
|
410
310
|
tok.match?(/^<<[-~]/) ? base_indent + indent : indent
|
|
411
311
|
elsif tok.match?(/^<<~/)
|
|
@@ -425,15 +325,15 @@ module IRB
|
|
|
425
325
|
end
|
|
426
326
|
end
|
|
427
327
|
|
|
428
|
-
def
|
|
429
|
-
|
|
430
|
-
LTYPE_TOKENS.include?(
|
|
328
|
+
def ltype_from_open_nestings(opens)
|
|
329
|
+
start_nesting = opens.reverse_each.find do |elem|
|
|
330
|
+
LTYPE_TOKENS.include?(elem.event)
|
|
431
331
|
end
|
|
432
|
-
return nil unless
|
|
332
|
+
return nil unless start_nesting
|
|
433
333
|
|
|
434
|
-
case
|
|
334
|
+
case start_nesting&.event
|
|
435
335
|
when :on_tstring_beg
|
|
436
|
-
case
|
|
336
|
+
case start_nesting&.tok
|
|
437
337
|
when ?" then ?"
|
|
438
338
|
when /^%.$/ then ?"
|
|
439
339
|
when /^%Q.$/ then ?"
|
|
@@ -448,50 +348,51 @@ module IRB
|
|
|
448
348
|
when :on_qsymbols_beg then ?]
|
|
449
349
|
when :on_symbols_beg then ?]
|
|
450
350
|
when :on_heredoc_beg
|
|
451
|
-
|
|
351
|
+
start_nesting&.tok =~ /<<[-~]?(['"`])\w+\1/
|
|
452
352
|
$1 || ?"
|
|
453
353
|
else
|
|
454
354
|
nil
|
|
455
355
|
end
|
|
456
356
|
end
|
|
457
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
|
|
458
374
|
def check_termination_in_prev_line(code, local_variables:)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
index = tokens.rindex do |t|
|
|
462
|
-
# traverse first token before last line
|
|
463
|
-
if past_first_newline
|
|
464
|
-
if t.tok.include?("\n")
|
|
465
|
-
true
|
|
466
|
-
end
|
|
467
|
-
elsif t.tok.include?("\n")
|
|
468
|
-
past_first_newline = true
|
|
469
|
-
false
|
|
470
|
-
else
|
|
471
|
-
false
|
|
472
|
-
end
|
|
473
|
-
end
|
|
375
|
+
lines = code.lines
|
|
376
|
+
return false if lines.size < 2
|
|
474
377
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
|
|
478
|
-
last_line_tokens.each do |t|
|
|
479
|
-
unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event)
|
|
480
|
-
first_token = t
|
|
481
|
-
break
|
|
482
|
-
end
|
|
483
|
-
end
|
|
378
|
+
prev_line_result = Prism.parse(lines[...-1].join, scopes: [local_variables])
|
|
379
|
+
return false unless prev_line_result.success?
|
|
484
380
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
end
|
|
492
|
-
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
|
|
493
387
|
end
|
|
494
|
-
|
|
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
|
|
495
396
|
end
|
|
496
397
|
end
|
|
497
398
|
# :startdoc:
|