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/color.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'reline'
|
|
3
|
-
require '
|
|
3
|
+
require 'prism'
|
|
4
4
|
require_relative 'ruby-lex'
|
|
5
5
|
|
|
6
6
|
module IRB # :nodoc:
|
|
@@ -18,64 +18,102 @@ module IRB # :nodoc:
|
|
|
18
18
|
CYAN = 36
|
|
19
19
|
WHITE = 37
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
21
|
+
# Following pry's colors where possible
|
|
22
|
+
TOKEN_SEQS = {
|
|
23
|
+
KEYWORD_NIL: [CYAN, BOLD],
|
|
24
|
+
KEYWORD_SELF: [CYAN, BOLD],
|
|
25
|
+
KEYWORD_TRUE: [CYAN, BOLD],
|
|
26
|
+
KEYWORD_FALSE: [CYAN, BOLD],
|
|
27
|
+
KEYWORD___FILE__: [CYAN, BOLD],
|
|
28
|
+
KEYWORD___LINE__: [CYAN, BOLD],
|
|
29
|
+
KEYWORD___ENCODING__: [CYAN, BOLD],
|
|
30
|
+
CHARACTER_LITERAL: [BLUE, BOLD],
|
|
31
|
+
BACK_REFERENCE: [GREEN, BOLD],
|
|
32
|
+
BACKTICK: [RED, BOLD],
|
|
33
|
+
COMMENT: [BLUE, BOLD],
|
|
34
|
+
EMBDOC_BEGIN: [BLUE, BOLD],
|
|
35
|
+
EMBDOC_LINE: [BLUE, BOLD],
|
|
36
|
+
EMBDOC_END: [BLUE, BOLD],
|
|
37
|
+
CONSTANT: [BLUE, BOLD, UNDERLINE],
|
|
38
|
+
EMBEXPR_BEGIN: [RED],
|
|
39
|
+
EMBEXPR_END: [RED],
|
|
40
|
+
EMBVAR: [RED],
|
|
41
|
+
FLOAT: [MAGENTA, BOLD],
|
|
42
|
+
GLOBAL_VARIABLE: [GREEN, BOLD],
|
|
43
|
+
HEREDOC_START: [RED],
|
|
44
|
+
HEREDOC_END: [RED],
|
|
45
|
+
FLOAT_IMAGINARY: [BLUE, BOLD],
|
|
46
|
+
INTEGER_IMAGINARY: [BLUE, BOLD],
|
|
47
|
+
FLOAT_RATIONAL_IMAGINARY: [BLUE, BOLD],
|
|
48
|
+
INTEGER_RATIONAL_IMAGINARY: [BLUE, BOLD],
|
|
49
|
+
INTEGER: [BLUE, BOLD],
|
|
50
|
+
INTEGER_RATIONAL: [BLUE, BOLD],
|
|
51
|
+
FLOAT_RATIONAL: [BLUE, BOLD],
|
|
52
|
+
KEYWORD_END: [GREEN],
|
|
53
|
+
KEYWORD_CLASS: [GREEN],
|
|
54
|
+
KEYWORD_MODULE: [GREEN],
|
|
55
|
+
KEYWORD_IF: [GREEN],
|
|
56
|
+
KEYWORD_IF_MODIFIER: [GREEN],
|
|
57
|
+
KEYWORD_UNLESS_MODIFIER: [GREEN],
|
|
58
|
+
KEYWORD_WHILE_MODIFIER: [GREEN],
|
|
59
|
+
KEYWORD_UNTIL_MODIFIER: [GREEN],
|
|
60
|
+
KEYWORD_RESCUE_MODIFIER: [GREEN],
|
|
61
|
+
KEYWORD_THEN: [GREEN],
|
|
62
|
+
KEYWORD_UNLESS: [GREEN],
|
|
63
|
+
KEYWORD_ELSE: [GREEN],
|
|
64
|
+
KEYWORD_ELSIF: [GREEN],
|
|
65
|
+
KEYWORD_WHILE: [GREEN],
|
|
66
|
+
KEYWORD_UNTIL: [GREEN],
|
|
67
|
+
KEYWORD_CASE: [GREEN],
|
|
68
|
+
KEYWORD_WHEN: [GREEN],
|
|
69
|
+
KEYWORD_IN: [GREEN],
|
|
70
|
+
KEYWORD_DEF: [GREEN],
|
|
71
|
+
KEYWORD_DO: [GREEN],
|
|
72
|
+
KEYWORD_DO_BLOCK: [GREEN],
|
|
73
|
+
KEYWORD_DO_LOOP: [GREEN],
|
|
74
|
+
KEYWORD_FOR: [GREEN],
|
|
75
|
+
KEYWORD_BEGIN: [GREEN],
|
|
76
|
+
KEYWORD_RESCUE: [GREEN],
|
|
77
|
+
KEYWORD_ENSURE: [GREEN],
|
|
78
|
+
KEYWORD_ALIAS: [GREEN],
|
|
79
|
+
KEYWORD_UNDEF: [GREEN],
|
|
80
|
+
KEYWORD_BEGIN_UPCASE: [GREEN],
|
|
81
|
+
KEYWORD_END_UPCASE: [GREEN],
|
|
82
|
+
KEYWORD_YIELD: [GREEN],
|
|
83
|
+
KEYWORD_REDO: [GREEN],
|
|
84
|
+
KEYWORD_RETRY: [GREEN],
|
|
85
|
+
KEYWORD_NEXT: [GREEN],
|
|
86
|
+
KEYWORD_BREAK: [GREEN],
|
|
87
|
+
KEYWORD_SUPER: [GREEN],
|
|
88
|
+
KEYWORD_RETURN: [GREEN],
|
|
89
|
+
KEYWORD_DEFINED: [GREEN],
|
|
90
|
+
KEYWORD_NOT: [GREEN],
|
|
91
|
+
KEYWORD_AND: [GREEN],
|
|
92
|
+
KEYWORD_OR: [GREEN],
|
|
93
|
+
LABEL: [MAGENTA],
|
|
94
|
+
LABEL_END: [RED, BOLD],
|
|
95
|
+
NUMBERED_REFERENCE: [GREEN, BOLD],
|
|
96
|
+
PERCENT_UPPER_W: [RED, BOLD],
|
|
97
|
+
PERCENT_LOWER_W: [RED, BOLD],
|
|
98
|
+
PERCENT_LOWER_X: [RED, BOLD],
|
|
99
|
+
REGEXP_BEGIN: [RED, BOLD],
|
|
100
|
+
REGEXP_END: [RED, BOLD],
|
|
101
|
+
STRING_BEGIN: [RED, BOLD],
|
|
102
|
+
STRING_CONTENT: [RED],
|
|
103
|
+
STRING_END: [RED, BOLD],
|
|
104
|
+
__END__: [GREEN],
|
|
105
|
+
# tokens from syntax tree traversal
|
|
106
|
+
method_name: [CYAN, BOLD],
|
|
107
|
+
message_name: [CYAN],
|
|
108
|
+
symbol: [YELLOW],
|
|
109
|
+
# special colorization
|
|
110
|
+
error: [RED, REVERSE],
|
|
111
|
+
}.transform_values do |styles|
|
|
112
|
+
styles.map { |style| "\e[#{style}m" }.join
|
|
74
113
|
end
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
private_constant :ERROR_TOKENS
|
|
114
|
+
CLEAR_SEQ = "\e[#{CLEAR}m"
|
|
115
|
+
OPERATORS = %i(!= !~ =~ == === <=> > >= < <= & | ^ >> << - + % / * ** -@ +@ ~ ! [] []=)
|
|
116
|
+
private_constant :TOKEN_SEQS, :CLEAR_SEQ, :OPERATORS
|
|
79
117
|
|
|
80
118
|
class << self
|
|
81
119
|
def colorable?
|
|
@@ -112,14 +150,13 @@ module IRB # :nodoc:
|
|
|
112
150
|
end
|
|
113
151
|
|
|
114
152
|
def clear(colorable: colorable?)
|
|
115
|
-
|
|
116
|
-
"\e[#{CLEAR}m"
|
|
153
|
+
colorable ? CLEAR_SEQ : ''
|
|
117
154
|
end
|
|
118
155
|
|
|
119
156
|
def colorize(text, seq, colorable: colorable?)
|
|
120
157
|
return text unless colorable
|
|
121
158
|
seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('')
|
|
122
|
-
"#{seq}#{text}#{
|
|
159
|
+
"#{seq}#{text}#{CLEAR_SEQ}"
|
|
123
160
|
end
|
|
124
161
|
|
|
125
162
|
# If `complete` is false (code is incomplete), this does not warn compile_error.
|
|
@@ -128,135 +165,187 @@ module IRB # :nodoc:
|
|
|
128
165
|
def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: [])
|
|
129
166
|
return code unless colorable
|
|
130
167
|
|
|
131
|
-
|
|
168
|
+
result = Prism.parse_lex(code, scopes: [local_variables])
|
|
169
|
+
|
|
170
|
+
# IRB::ColorPrinter skips colorizing syntax invalid fragments
|
|
171
|
+
return Reline::Unicode.escape_for_print(code) if ignore_error && !result.success?
|
|
172
|
+
|
|
173
|
+
prism_node, prism_tokens = result.value
|
|
174
|
+
errors = result.errors
|
|
175
|
+
|
|
176
|
+
unless complete
|
|
177
|
+
errors = filter_incomplete_code_errors(errors, prism_tokens)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
visitor = ColorizeVisitor.new
|
|
181
|
+
prism_node.accept(visitor)
|
|
182
|
+
|
|
183
|
+
error_tokens = errors.map { |e| [e.location.start_line, e.location.start_column, 0, e.location.end_line, e.location.end_column, :error, e.location.slice] }
|
|
184
|
+
error_tokens.reject! { |t| t.last.match?(/\A\s*\z/) }
|
|
185
|
+
tokens = prism_tokens.map { |t,| [t.location.start_line, t.location.start_column, 2, t.location.end_line, t.location.end_column, t.type, t.value] }
|
|
186
|
+
tokens.pop if tokens.last&.[](5) == :EOF
|
|
187
|
+
|
|
132
188
|
colored = +''
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
colored << Reline::Unicode.escape_for_print(
|
|
140
|
-
|
|
189
|
+
line_index = 0
|
|
190
|
+
col = 0
|
|
191
|
+
lines = code.lines
|
|
192
|
+
flush = -> next_line_index, next_col {
|
|
193
|
+
return if next_line_index == line_index && next_col == col
|
|
194
|
+
(line_index...[next_line_index, lines.size].min).each do |ln|
|
|
195
|
+
colored << Reline::Unicode.escape_for_print(lines[line_index].byteslice(col..))
|
|
196
|
+
line_index = ln + 1
|
|
197
|
+
col = 0
|
|
141
198
|
end
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if ignore_error && ERROR_TOKENS.include?(token)
|
|
145
|
-
return Reline::Unicode.escape_for_print(code)
|
|
199
|
+
unless col == next_col
|
|
200
|
+
colored << Reline::Unicode.escape_for_print(lines[next_line_index].byteslice(col..next_col - 1))
|
|
146
201
|
end
|
|
202
|
+
}
|
|
147
203
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
204
|
+
(visitor.tokens + tokens + error_tokens).sort.each do |start_line, start_column, _priority, end_line, end_column, type, value|
|
|
205
|
+
next if start_line - 1 < line_index || (start_line - 1 == line_index && start_column < col)
|
|
206
|
+
|
|
207
|
+
flush.call(start_line - 1, start_column)
|
|
208
|
+
if type == :__END__
|
|
209
|
+
color = TOKEN_SEQS[type]
|
|
210
|
+
end_line = start_line
|
|
211
|
+
value = '__END__'
|
|
212
|
+
end_column = start_column + 7
|
|
213
|
+
else
|
|
214
|
+
color = TOKEN_SEQS[type]
|
|
215
|
+
end
|
|
216
|
+
if color
|
|
217
|
+
value.split(/(\n)/).each do |s|
|
|
218
|
+
colored << (s == "\n" ? s : "#{color}#{Reline::Unicode.escape_for_print(s)}#{CLEAR_SEQ}")
|
|
156
219
|
end
|
|
220
|
+
else
|
|
221
|
+
colored << value
|
|
157
222
|
end
|
|
223
|
+
line_index = end_line - 1
|
|
224
|
+
col = end_column
|
|
158
225
|
end
|
|
159
|
-
|
|
160
|
-
if lvars_code
|
|
161
|
-
raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n")
|
|
162
|
-
colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors
|
|
163
|
-
end
|
|
226
|
+
flush.call lines.size, 0
|
|
164
227
|
colored
|
|
165
228
|
end
|
|
166
229
|
|
|
167
|
-
|
|
230
|
+
class ColorizeVisitor < Prism::Visitor
|
|
231
|
+
attr_reader :tokens
|
|
232
|
+
def initialize
|
|
233
|
+
@tokens = []
|
|
234
|
+
end
|
|
168
235
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
seen.delete(obj)
|
|
175
|
-
end
|
|
236
|
+
def dispatch(location, type)
|
|
237
|
+
if location
|
|
238
|
+
@tokens << [location.start_line, location.start_column, 1, location.end_line, location.end_column, type, location.slice]
|
|
239
|
+
end
|
|
240
|
+
end
|
|
176
241
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
byte_pos = 0
|
|
182
|
-
line_positions = [0]
|
|
183
|
-
inner_code.lines.each do |line|
|
|
184
|
-
line_positions << line_positions.last + line.bytesize
|
|
242
|
+
def visit_array_node(node)
|
|
243
|
+
if node.opening&.match?(/\A%[iI]/)
|
|
244
|
+
dispatch node.opening_loc, :symbol
|
|
245
|
+
dispatch node.closing_loc, :symbol
|
|
185
246
|
end
|
|
247
|
+
super
|
|
248
|
+
end
|
|
186
249
|
|
|
187
|
-
|
|
188
|
-
|
|
250
|
+
def visit_def_node(node)
|
|
251
|
+
dispatch node.name_loc, :method_name
|
|
252
|
+
super
|
|
253
|
+
end
|
|
189
254
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
255
|
+
def visit_alias_method_node(node)
|
|
256
|
+
dispatch_alias_method_name node.new_name
|
|
257
|
+
dispatch_alias_method_name node.old_name
|
|
258
|
+
super
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def visit_call_node(node)
|
|
262
|
+
if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
|
|
263
|
+
# Operators should not be colored as method call
|
|
264
|
+
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
|
|
265
|
+
/\A\p{Upper}/.match?(node.name)
|
|
266
|
+
# Constant-like methods should not be colored as method call
|
|
267
|
+
else
|
|
268
|
+
dispatch node.message_loc, :message_name
|
|
269
|
+
end
|
|
270
|
+
super
|
|
271
|
+
end
|
|
194
272
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
273
|
+
def visit_call_operator_write_node(node)
|
|
274
|
+
dispatch node.message_loc, :message_name
|
|
275
|
+
super
|
|
276
|
+
end
|
|
277
|
+
alias visit_call_and_write_node visit_call_operator_write_node
|
|
278
|
+
alias visit_call_or_write_node visit_call_operator_write_node
|
|
279
|
+
|
|
280
|
+
def visit_interpolated_symbol_node(node)
|
|
281
|
+
dispatch node.opening_loc, :symbol
|
|
282
|
+
node.parts.each do |part|
|
|
283
|
+
case part
|
|
284
|
+
when Prism::StringNode
|
|
285
|
+
dispatch part.content_loc, :symbol
|
|
286
|
+
when Prism::EmbeddedStatementsNode
|
|
287
|
+
dispatch part.opening_loc, :symbol
|
|
288
|
+
dispatch part.closing_loc, :symbol
|
|
289
|
+
when Prism::EmbeddedVariableNode
|
|
290
|
+
dispatch part.operator_loc, :symbol
|
|
199
291
|
end
|
|
200
292
|
end
|
|
293
|
+
dispatch node.closing_loc, :symbol
|
|
294
|
+
super
|
|
295
|
+
end
|
|
201
296
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
297
|
+
def visit_symbol_node(node)
|
|
298
|
+
if (node.opening_loc.nil? && node.closing == ':') || node.closing&.match?(/\A['"]:\z/)
|
|
299
|
+
# Colorize { symbol: 1 } and { 'symbol': 1 } as label
|
|
300
|
+
dispatch node.location, :LABEL
|
|
301
|
+
else
|
|
302
|
+
dispatch node.opening_loc, :symbol
|
|
303
|
+
dispatch node.value_loc, :symbol
|
|
304
|
+
dispatch node.closing_loc, :symbol
|
|
205
305
|
end
|
|
206
|
-
# yield uncolorable DATA section
|
|
207
|
-
yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize
|
|
208
306
|
end
|
|
209
|
-
ensure
|
|
210
|
-
$VERBOSE = verbose
|
|
211
|
-
end
|
|
212
307
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
[CYAN, BOLD]
|
|
220
|
-
elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0)
|
|
221
|
-
seq
|
|
222
|
-
else
|
|
223
|
-
nil
|
|
308
|
+
private
|
|
309
|
+
|
|
310
|
+
def dispatch_alias_method_name(node)
|
|
311
|
+
if node.type == :symbol_node && node.opening_loc.nil?
|
|
312
|
+
dispatch node.value_loc, :method_name
|
|
313
|
+
end
|
|
224
314
|
end
|
|
225
315
|
end
|
|
226
|
-
end
|
|
227
316
|
|
|
228
|
-
|
|
229
|
-
class SymbolState
|
|
230
|
-
def initialize
|
|
231
|
-
# Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol.
|
|
232
|
-
@stack = []
|
|
233
|
-
end
|
|
317
|
+
private
|
|
234
318
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return prev_state
|
|
251
|
-
when :on_tstring_end # :on_tstring_end may close Symbol
|
|
252
|
-
@stack.pop
|
|
253
|
-
return prev_state
|
|
254
|
-
when :on_embexpr_end
|
|
255
|
-
@stack.pop
|
|
319
|
+
FILTERED_ERROR_TYPES = [
|
|
320
|
+
:class_name, :module_name, # `class`, `class owner_module`
|
|
321
|
+
:write_target_unexpected, # `a, b`
|
|
322
|
+
:parameter_wild_loose_comma, # `def f(a,`
|
|
323
|
+
:argument_no_forwarding_star, # `[*`
|
|
324
|
+
:argument_no_forwarding_star_star, # `f(**`
|
|
325
|
+
:argument_no_forwarding_ampersand, # `f(&`
|
|
326
|
+
:def_endless, # `def f =`
|
|
327
|
+
:embdoc_term, # `=begin`
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
# Filter out syntax errors that are likely to be caused by incomplete code, to avoid showing misleading error highlights to users.
|
|
331
|
+
def filter_incomplete_code_errors(errors, tokens)
|
|
332
|
+
last_non_comment_space_token, = tokens.reverse_each.find do |t,|
|
|
333
|
+
t.type != :COMMENT && t.type != :EOF && t.type != :IGNORED_NEWLINE && t.type != :NEWLINE
|
|
256
334
|
end
|
|
257
|
-
|
|
335
|
+
last_offset = last_non_comment_space_token ? last_non_comment_space_token.location.end_offset : 0
|
|
336
|
+
errors.reject do |error|
|
|
337
|
+
error.message.match?(/\Aexpected a|unexpected end-of-input|unterminated/) ||
|
|
338
|
+
(error.location.end_offset == last_offset && FILTERED_ERROR_TYPES.include?(error.type))
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def without_circular_ref(obj, seen:, &block)
|
|
343
|
+
return false if seen.key?(obj)
|
|
344
|
+
seen[obj] = true
|
|
345
|
+
block.call
|
|
346
|
+
ensure
|
|
347
|
+
seen.delete(obj)
|
|
258
348
|
end
|
|
259
349
|
end
|
|
260
|
-
private_constant :SymbolState
|
|
261
350
|
end
|
|
262
351
|
end
|
data/lib/irb/color_printer.rb
CHANGED
|
@@ -4,12 +4,9 @@ require_relative 'color'
|
|
|
4
4
|
|
|
5
5
|
module IRB
|
|
6
6
|
class ColorPrinter < ::PP
|
|
7
|
-
METHOD_RESPOND_TO = Object.instance_method(:respond_to?)
|
|
8
|
-
METHOD_INSPECT = Object.instance_method(:inspect)
|
|
9
|
-
|
|
10
7
|
class << self
|
|
11
|
-
def pp(obj, out = $>, width = screen_width)
|
|
12
|
-
q = ColorPrinter.new(out, width)
|
|
8
|
+
def pp(obj, out = $>, width = screen_width, colorize: true)
|
|
9
|
+
q = ColorPrinter.new(out, width, colorize: colorize)
|
|
13
10
|
q.guard_inspect_key {q.pp obj}
|
|
14
11
|
q.flush
|
|
15
12
|
out << "\n"
|
|
@@ -24,12 +21,16 @@ module IRB
|
|
|
24
21
|
end
|
|
25
22
|
end
|
|
26
23
|
|
|
24
|
+
def initialize(out, width, colorize: true)
|
|
25
|
+
@colorize = colorize
|
|
26
|
+
|
|
27
|
+
super(out, width)
|
|
28
|
+
end
|
|
29
|
+
|
|
27
30
|
def pp(obj)
|
|
28
31
|
if String === obj
|
|
29
32
|
# Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n"
|
|
30
33
|
text(obj.inspect)
|
|
31
|
-
elsif !METHOD_RESPOND_TO.bind(obj).call(:inspect)
|
|
32
|
-
text(METHOD_INSPECT.bind(obj).call)
|
|
33
34
|
else
|
|
34
35
|
super
|
|
35
36
|
end
|
|
@@ -46,9 +47,9 @@ module IRB
|
|
|
46
47
|
when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/
|
|
47
48
|
super(str, width)
|
|
48
49
|
when /\A#</, '=', '>'
|
|
49
|
-
super(Color.colorize(str, [:GREEN]), width)
|
|
50
|
+
super(@colorize ? Color.colorize(str, [:GREEN]) : str, width)
|
|
50
51
|
else
|
|
51
|
-
super(Color.colorize_code(str, ignore_error: true), width)
|
|
52
|
+
super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width)
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
end
|
data/lib/irb/command/base.rb
CHANGED
|
@@ -37,11 +37,46 @@ module IRB
|
|
|
37
37
|
puts e.message
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Returns formatted lines for display in the doc dialog popup.
|
|
41
|
+
def doc_dialog_content(name, width)
|
|
42
|
+
lines = []
|
|
43
|
+
lines << Color.colorize(name, [:BOLD, :BLUE]) + Color.colorize(" (command)", [:CYAN])
|
|
44
|
+
lines << ""
|
|
45
|
+
lines.concat(wrap_lines(description, width))
|
|
46
|
+
if help_message
|
|
47
|
+
lines << ""
|
|
48
|
+
lines.concat(wrap_lines(help_message, width))
|
|
49
|
+
end
|
|
50
|
+
lines
|
|
51
|
+
end
|
|
52
|
+
|
|
40
53
|
private
|
|
41
54
|
|
|
42
55
|
def highlight(text)
|
|
43
56
|
Color.colorize(text, [:BOLD, :BLUE])
|
|
44
57
|
end
|
|
58
|
+
|
|
59
|
+
def wrap_lines(text, width)
|
|
60
|
+
text.lines.flat_map do |line|
|
|
61
|
+
line = line.chomp
|
|
62
|
+
next [''] if line.empty?
|
|
63
|
+
next [line] if line.length <= width
|
|
64
|
+
|
|
65
|
+
indent = line[/\A\s*/]
|
|
66
|
+
parts = line.strip.split(/(\s+)/)
|
|
67
|
+
result = []
|
|
68
|
+
current = indent.dup
|
|
69
|
+
parts.each do |part|
|
|
70
|
+
if current != indent && current.length + part.length > width
|
|
71
|
+
result << current.rstrip
|
|
72
|
+
current = indent.dup
|
|
73
|
+
end
|
|
74
|
+
current << part unless current == indent && part.match?(/\A\s+\z/)
|
|
75
|
+
end
|
|
76
|
+
result << current.rstrip unless current == indent
|
|
77
|
+
result
|
|
78
|
+
end
|
|
79
|
+
end
|
|
45
80
|
end
|
|
46
81
|
|
|
47
82
|
def initialize(irb_context)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IRB
|
|
4
|
+
module Command
|
|
5
|
+
class Copy < Base
|
|
6
|
+
category "Misc"
|
|
7
|
+
description "Copy expression output to clipboard"
|
|
8
|
+
|
|
9
|
+
help_message(<<~HELP)
|
|
10
|
+
Usage: copy ([expression])
|
|
11
|
+
|
|
12
|
+
When given:
|
|
13
|
+
- an expression, copy the inspect result of the expression to the clipboard.
|
|
14
|
+
- no arguments, copy the last evaluated result (`_`) to the clipboard.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
|
|
18
|
+
copy Foo.new
|
|
19
|
+
copy User.all.to_a
|
|
20
|
+
copy
|
|
21
|
+
HELP
|
|
22
|
+
|
|
23
|
+
def execute(arg)
|
|
24
|
+
# Copy last value if no expression was supplied
|
|
25
|
+
arg = '_' if arg.to_s.strip.empty?
|
|
26
|
+
|
|
27
|
+
value = irb_context.workspace.binding.eval(arg)
|
|
28
|
+
output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp
|
|
29
|
+
|
|
30
|
+
if clipboard_available?
|
|
31
|
+
copy_to_clipboard(output)
|
|
32
|
+
else
|
|
33
|
+
warn "System clipboard not found"
|
|
34
|
+
end
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
warn "Error: #{e}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def copy_to_clipboard(text)
|
|
42
|
+
IO.popen(clipboard_program, 'w') do |io|
|
|
43
|
+
io.write(text)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
raise IOError.new("Copying to clipboard failed") unless $? == 0
|
|
47
|
+
|
|
48
|
+
puts "Copied to system clipboard"
|
|
49
|
+
rescue Errno::ENOENT => e
|
|
50
|
+
warn e.message
|
|
51
|
+
warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clipboard_program
|
|
55
|
+
@clipboard_program ||= if IRB.conf[:COPY_COMMAND]
|
|
56
|
+
IRB.conf[:COPY_COMMAND]
|
|
57
|
+
elsif executable?("clip.exe")
|
|
58
|
+
"clip.exe"
|
|
59
|
+
elsif executable?("pbcopy")
|
|
60
|
+
"pbcopy"
|
|
61
|
+
elsif executable?("xclip")
|
|
62
|
+
"xclip -selection clipboard"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def executable?(command)
|
|
67
|
+
if windows?
|
|
68
|
+
system("where #{command} > NUL 2>&1")
|
|
69
|
+
else
|
|
70
|
+
system("which #{command} > /dev/null 2>&1")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def clipboard_available?
|
|
75
|
+
!!clipboard_program
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def windows?
|
|
79
|
+
/mingw|mswin/.match?(RUBY_PLATFORM)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|