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